<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>ScoreStarling 博客</title>
    <link>https://scorestarling.com/zh-hans/blog</link>
    <description>关于把录音变成可编辑乐谱的研究、指南和故事。</description>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 08 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://scorestarling.com/zh-hans/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <image>
      <url>https://scorestarling.com/assets/starling-v2.png</url>
      <title>ScoreStarling 博客</title>
      <link>https://scorestarling.com/zh-hans/blog</link>
    </image>
    <item>
      <title>吉他六线谱怎么决定按哪根弦、哪一品</title>
      <link>https://scorestarling.com/zh-hans/blog/guitar-tab-strings-and-frets</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/guitar-tab-strings-and-frets</guid>
      <pubDate>Thu, 08 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>指南</category>
      <description>同一个音在吉他指板上最多有六个位置，MuseScore 等打谱软件会选能弹出它的音最高的那根弦，也就是最低的品。本文还讲了这样排什么时候不顺手，以及 MusicXML 生成的六线谱为什么会高一个八度。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>同一个音高通常能在二到六根弦上弹出来，所以六线谱必须选定一根。大多数打谱软件（包括 MuseScore）会把每个音放在能弹出它的音最高的那根弦上，也就是最低的品；和弦里某个音的弦如果已被占用，就挪到下一根空着的弦。这样排出的指法把位低、好读，但也可能让旋律在一根弦上一路往上爬，而不是停在同一个把位里。如果从文件生成的六线谱整体高了一个八度，那是因为文件把吉他记得比实际音高高一个八度，而软件按记谱音高排了品。</p>
</section>

<h2 id="why-several-places">为什么同一个音在指板上有好几个位置？</h2>
<p>因为各弦的音域相互重叠。标准定弦下，六根空弦依次是 E2、A2、D3、G3、B3 和 E4，相邻两弦相差五个半音，只有 G 弦到 B 弦相差四个，所以一根弦的第 5 品（G 弦是第 4 品）就是下一根弦的空弦音。中央 C 上方的 E，也就是 E4，可以是 1 弦空弦、B 弦第 5 品、G 弦第 9 品、D 弦第 14 品、A 弦第 19 品，或者低音 E 弦第 24 品：24 品的琴颈上有六个位置，22 品的电吉他或 19 品的古典吉他上则有五个。</p>
<figure class="diagram">
<svg viewBox="0 0 320 150" aria-hidden="true" focusable="false">
<text class="d-small" x="10" y="34">e</text>
<text class="d-small" x="10" y="49">B</text>
<text class="d-small" x="10" y="64">G</text>
<text class="d-small" x="10" y="79">D</text>
<text class="d-small" x="10" y="94">A</text>
<text class="d-small" x="10" y="109">E</text>
<line class="d-line" x1="30" y1="30" x2="310" y2="30"/>
<line class="d-line" x1="30" y1="45" x2="310" y2="45"/>
<line class="d-line" x1="30" y1="60" x2="310" y2="60"/>
<line class="d-line" x1="30" y1="75" x2="310" y2="75"/>
<line class="d-line" x1="30" y1="90" x2="310" y2="90"/>
<line class="d-line" x1="30" y1="105" x2="310" y2="105"/>
<line class="d-frame" x1="30" y1="26" x2="30" y2="109"/>
<line class="d-line d-dash" x1="88.3" y1="26" x2="88.3" y2="109"/>
<line class="d-line d-dash" x1="170" y1="26" x2="170" y2="109"/>
<line class="d-line d-dash" x1="310" y1="26" x2="310" y2="109"/>
<circle class="d-mark" cx="24" cy="30" r="5"/>
<circle class="d-mark" cx="88.3" cy="45" r="5"/>
<circle class="d-mark" cx="135" cy="60" r="5"/>
<circle class="d-mark" cx="193.3" cy="75" r="5"/>
<circle class="d-mark" cx="251.7" cy="90" r="5"/>
<circle class="d-mark" cx="310" cy="105" r="5"/>
<text class="d-small" x="30" y="128" text-anchor="middle">琴枕</text>
<text class="d-small" x="88.3" y="128" text-anchor="middle">5</text>
<text class="d-small" x="170" y="128" text-anchor="middle">12</text>
<text class="d-small" x="310" y="128" text-anchor="middle">24</text>
<text class="d-small" x="160" y="146" text-anchor="middle">品丝</text>
</svg>
<figcaption>标准定弦下 E4 的六个位置：1 弦空弦，以及往下各弦的第 5、9、14、19 和 24 品。</figcaption>
</figure>
<p>五线谱只给出音高，六线谱给的是弦和品。所以不管是人还是程序来写六线谱，都要为每个音从这些位置里挑一个，而这个选择决定了手放在哪里。</p>

<h2 id="what-rule-apps-use">打谱软件按什么规则选弦？</h2>
<p>MuseScore 的规则写在源代码的 <code>StringData</code> 里。一个音会被放到空弦音不高于它的弦中音最高的那一根（<code>convertPitch</code> 上方的注释写着“using the highest possible string”，即尽量用最高的弦）。和弦则从最高的音往下逐个安排；如果某个音要用的弦已被占用，软件会“从最上面开始找一根合适的弦”（a suitable string, from topmost），也就是从上往下找第一根空着、并且能在指板品数范围内弹出这个音的弦。如果一根也找不到，两个音就留在同一根弦上，六线谱会标出这个冲突。</p>
<p>我们为 ScoreStarling 写了同样的规则，并把两者做了对比。一个脚本在标准定弦、还没填品的六线谱谱表上随机写入 120 个和弦，每个和弦含一到四个不同的音，音域在 E2 到 C6 之间；再让 MuseScore 4.7.5 的命令行把品填上，然后逐个音比对。我们换不同的随机和弦，一共跑了四次。</p>
<div class="table-scroll" role="region" aria-label="480 个随机和弦上，ScoreStarling 默认的弦和品与 MuseScore 的对比" tabindex="0">
<table>
<caption>480 个随机和弦：MuseScore 4.7.5 对比 ScoreStarling 默认规则，2026年10月8日</caption>
<thead><tr><th scope="col">和弦</th><th scope="col" class="num">数量</th><th scope="col">结果</th></tr></thead>
<tbody>
<tr><th scope="row">MuseScore 能排出来的</th><td class="num">447</td><td>全部 1,055 个音的弦和品都相同</td></tr>
<tr><th scope="row">MuseScore 把两个音留在同一根弦上的</th><td class="num">13</td><td>高把位和弦；它导入的吉他品数比我们的 24 品少，而我们把每个音都放在了各自的弦上</td></tr>
<tr><th scope="row">怎么排弦都弹不了的</th><td class="num">20</td><td>低音比能弹到它们的弦还多，比如两个低于 A2 的音，只有低音 E 弦能弹；按 MuseScore 的写法写出并加上标记</td></tr>
</tbody>
</table>
</div>
<p>这条规则可预测，所以很适合当默认值：看六线谱的人都能推算出每个音会落在哪里。用 Guitar Pro 或 MuseScore 打开这份乐谱，看到的品也一样。</p>

<h2 id="when-lowest-reads-badly">“低品优先”什么时候会排得别扭？</h2>
<p>当旋律落在指板高处时。这条规则从不看前后的音，所以凡是 1 弦能弹的音都会放到 1 弦上，吉他手本来会在一个位置弹完的乐句，就变成在这一根弦上来回跑。下面是一个从第 12 品的 E 往下走的乐句，ScoreStarling 分别按两种规则写出。</p>
<figure class="tab-compare">
  <div class="tab-compare-row"><p class="tab-compare-name">低品优先</p><div class="notation-sheet"><!-- engrave:phrase-lowest --><svg class="engraved" viewBox="0 0 1100 313" role="img" aria-label="这个乐句按低品优先写成的六线谱：所有音都在 1 弦上，从第 12 品下行到第 3 品" focusable="false"><svg class="definition-scale" viewBox="0 0 11000 3130"><g class="page-margin" transform="translate(0, 80)"><g class="system"><g class="measure"><g class="staff"><path d="M0 942 L1677 942" stroke-width="23"/><path d="M1929 942 L2647 942" stroke-width="23"/><path d="M3061 942 L3698 942" stroke-width="23"/><path d="M4112 942 L4749 942" stroke-width="23"/><path d="M5163 942 L6020 942" stroke-width="23"/><path d="M0 1257 L6020 1257" stroke-width="23"/><path d="M0 1572 L6020 1572" stroke-width="23"/><path d="M0 1887 L6020 1887" stroke-width="23"/><path d="M0 2202 L6020 2202" stroke-width="23"/><path d="M0 2517 L6020 2517" stroke-width="23"/><g class="clef"><use href="#ss-E06D" transform="translate(159, 1727) scale(0.72, 0.72)"/></g><g class="meterSig"><use href="#ss-E084" transform="translate(752, 1547) scale(0.72, 0.72)"/><use href="#ss-E084" transform="translate(752, 1907) scale(0.72, 0.72)"/></g><g class="layer"><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M1803 785 L1803 157" stroke-width="31"/></g></g><g data-id="p1" class="note"><text x="1803" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">8</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M2854 785 L2854 157" stroke-width="31"/></g></g><g data-id="p2" class="note"><text x="2854" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">10</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M3905 785 L3905 157" stroke-width="31"/></g></g><g data-id="p3" class="note"><text x="3905" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">12</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M4956 785 L4956 157" stroke-width="31"/></g></g><g data-id="p4" class="note"><text x="4956" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">10</tspan></text></g></g></g></g><g class="barLine"><path d="M6007 942 L6007 2512" stroke-width="27"/></g></g><g class="measure"><g class="staff"><path d="M6020 942 L6496 942" stroke-width="23"/><path d="M6748 942 L7547 942" stroke-width="23"/><path d="M7799 942 L8598 942" stroke-width="23"/><path d="M8850 942 L9649 942" stroke-width="23"/><path d="M9901 942 L11001 942" stroke-width="23"/><path d="M6020 1257 L11001 1257" stroke-width="23"/><path d="M6020 1572 L11001 1572" stroke-width="23"/><path d="M6020 1887 L11001 1887" stroke-width="23"/><path d="M6020 2202 L11001 2202" stroke-width="23"/><path d="M6020 2517 L11001 2517" stroke-width="23"/><g class="layer"><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M6622 785 L6622 157" stroke-width="31"/></g></g><g data-id="p5" class="note"><text x="6622" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">8</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M7673 785 L7673 157" stroke-width="31"/></g></g><g data-id="p6" class="note"><text x="7673" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">7</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M8724 785 L8724 157" stroke-width="31"/></g></g><g data-id="p7" class="note"><text x="8724" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">5</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M9775 785 L9775 157" stroke-width="31"/></g></g><g data-id="p8" class="note"><text x="9775" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">3</tspan></text></g></g></g></g><g class="barLine"><path d="M10826 942 L10826 2512" stroke-width="27"/><path d="M10956 942 L10956 2512" stroke-width="90"/></g></g><g class="systemMilestoneEnd yhbsl03"/></g><g class="pageMilestoneEnd wtn72u"/><g class="pageMilestoneEnd v3bpgbh"/></g></svg></svg><!-- /engrave:phrase-lowest --></div></div>
  <div class="tab-compare-row"><p class="tab-compare-name">换把最少</p><div class="notation-sheet"><!-- engrave:phrase-hand --><svg class="engraved" viewBox="0 0 1100 313" role="img" aria-label="这个乐句按换把最少写成的六线谱：最后两个音在 B 弦的第 10 品和第 8 品" focusable="false"><svg class="definition-scale" viewBox="0 0 11000 3130"><g class="page-margin" transform="translate(0, 80)"><g class="system"><g class="measure"><g class="staff"><path d="M0 942 L1677 942" stroke-width="23"/><path d="M1929 942 L2647 942" stroke-width="23"/><path d="M3061 942 L3698 942" stroke-width="23"/><path d="M4112 942 L4749 942" stroke-width="23"/><path d="M5163 942 L6020 942" stroke-width="23"/><path d="M0 1257 L6020 1257" stroke-width="23"/><path d="M0 1572 L6020 1572" stroke-width="23"/><path d="M0 1887 L6020 1887" stroke-width="23"/><path d="M0 2202 L6020 2202" stroke-width="23"/><path d="M0 2517 L6020 2517" stroke-width="23"/><g class="clef"><use href="#ss-E06D" transform="translate(159, 1727) scale(0.72, 0.72)"/></g><g class="meterSig"><use href="#ss-E084" transform="translate(752, 1547) scale(0.72, 0.72)"/><use href="#ss-E084" transform="translate(752, 1907) scale(0.72, 0.72)"/></g><g class="layer"><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M1803 785 L1803 157" stroke-width="31"/></g></g><g data-id="p1" class="note"><text x="1803" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">8</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M2854 785 L2854 157" stroke-width="31"/></g></g><g data-id="p2" class="note"><text x="2854" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">10</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M3905 785 L3905 157" stroke-width="31"/></g></g><g data-id="p3" class="note"><text x="3905" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">12</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M4956 785 L4956 157" stroke-width="31"/></g></g><g data-id="p4" class="note"><text x="4956" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">10</tspan></text></g></g></g></g><g class="barLine"><path d="M6007 942 L6007 2512" stroke-width="27"/></g></g><g class="measure"><g class="staff"><path d="M6020 942 L6496 942" stroke-width="23"/><path d="M6748 942 L7547 942" stroke-width="23"/><path d="M7799 942 L11001 942" stroke-width="23"/><path d="M6020 1257 L8517 1257" stroke-width="23"/><path d="M8931 1257 L9649 1257" stroke-width="23"/><path d="M9901 1257 L11001 1257" stroke-width="23"/><path d="M6020 1572 L11001 1572" stroke-width="23"/><path d="M6020 1887 L11001 1887" stroke-width="23"/><path d="M6020 2202 L11001 2202" stroke-width="23"/><path d="M6020 2517 L11001 2517" stroke-width="23"/><g class="layer"><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M6622 785 L6622 157" stroke-width="31"/></g></g><g data-id="p5" class="note"><text x="6622" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">8</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M7673 785 L7673 157" stroke-width="31"/></g></g><g data-id="p6" class="note"><text x="7673" y="1052" text-anchor="middle" font-size="0px"><tspan font-size="324px">7</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M8724 785 L8724 157" stroke-width="31"/></g></g><g data-id="p7" class="note is-marked"><text x="8724" y="1366" text-anchor="middle" font-size="0px"><tspan font-size="324px">10</tspan></text></g></g><g class="tabGrp"><g class="tabDurSym"><g class="stem"><path d="M9775 785 L9775 157" stroke-width="31"/></g></g><g data-id="p8" class="note is-marked"><text x="9775" y="1366" text-anchor="middle" font-size="0px"><tspan font-size="324px">8</tspan></text></g></g></g></g><g class="barLine"><path d="M10826 942 L10826 2512" stroke-width="27"/><path d="M10956 942 L10956 2512" stroke-width="90"/></g></g><g class="systemMilestoneEnd yhbsl03"/></g><g class="pageMilestoneEnd wtn72u"/><g class="pageMilestoneEnd v3bpgbh"/></g></svg></svg><!-- /engrave:phrase-hand --></div></div>
  <figcaption>同样的八个音，由 ScoreStarling 按两种规则分别画出。红色的两个音挪到了 B 弦上，手因此能停在第 7 到第 12 品之间。</figcaption>
</figure>
<!-- engrave:glyphs --><svg class="glyph-sprite" width="0" height="0" aria-hidden="true" focusable="false"><defs><g id="ss-E06D"><path transform="scale(1,-1)" d="M18 697v53h369v-53h-154v-418h-60v418h-155zM111 -94l-50 -141h-64l168 470h78l165 -470h-67l-49 141h-181zM276 -43l-73 214l-77 -214h150zM27 -750v471h191c89 0 139 -51 139 -120c0 -47 -22 -82 -65 -102c60 -23 86 -58 86 -114c0 -78 -54 -135 -139 -135h-212z M203 -332h-116v-150h116c67 0 94 27 94 75s-27 75 -94 75zM234 -535h-147v-162h147c56 0 84 36 84 81c0 46 -28 81 -84 81z"/></g><g id="ss-E084"><path transform="scale(1,-1)" d="M20 -78c84 97 114 180 134 329h170c-13 -32 -82 -132 -99 -151l-84 -97c-33 -36 -59 -63 -80 -81h162v102l127 123v-225h57v-39h-57v-34c0 -43 19 -65 57 -65v-34h-244v36c48 0 60 26 60 70v27h-203v39z"/></g></defs></svg><!-- /engrave:glyphs -->
<div class="table-scroll" role="region" aria-label="两种选弦规则下的一个下行乐句" tabindex="0">
<table>
<caption>标准定弦下从 C5 开始的乐句：弦与品，1 弦为最高音的那根弦</caption>
<thead><tr><th scope="col">音</th><th scope="col">低品优先</th><th scope="col">换把最少</th></tr></thead>
<tbody>
<tr><th scope="row">C5</th><td>1 弦第 8 品</td><td>1 弦第 8 品</td></tr>
<tr><th scope="row">D5</th><td>1 弦第 10 品</td><td>1 弦第 10 品</td></tr>
<tr><th scope="row">E5</th><td>1 弦第 12 品</td><td>1 弦第 12 品</td></tr>
<tr><th scope="row">D5</th><td>1 弦第 10 品</td><td>1 弦第 10 品</td></tr>
<tr><th scope="row">C5</th><td>1 弦第 8 品</td><td>1 弦第 8 品</td></tr>
<tr><th scope="row">B4</th><td>1 弦第 7 品</td><td>1 弦第 7 品</td></tr>
<tr><th scope="row">A4</th><td>1 弦第 5 品</td><td>2 弦第 10 品</td></tr>
<tr><th scope="row">G4</th><td>1 弦第 3 品</td><td>2 弦第 8 品</td></tr>
</tbody>
</table>
</div>
<p>低品优先的排法在一根弦上横跨第 3 到第 12 品，下行时手要一次又一次地移动。换把最少的排法把最后两个音放在 B 弦的第 10 品和第 8 品，手始终保持在第 7 到第 12 品之间。两种都不算错，大多数打谱软件显示的是低品优先的版本；第二种更接近老师会写的指法。</p>
<p>程序只要通盘规划整个声部，就能找到第二种排法。ScoreStarling 的“换把最少”选项把每个音或和弦当作一步，列出它所有可能的按法，再在所有步骤中搜索代价最小的路径：手一次覆盖四个品；换把移得越远，代价越高；跨度超过四个品的和弦要额外加代价；高把位和第 12 品以上的位置稍微加一点；空弦不计代价。不过它仍然不了解你的演奏技术，所以规划出的指法只是建议，还需要你核对。</p>

<h2 id="octave-too-high">为什么我的六线谱高了一个八度？</h2>
<p>因为吉他的实际音高比记谱低一个八度，而文件有两种方式表达这一点。MusicXML 可以按实际音高记谱，用下方带小 8 的高音谱号；也可以高八度记谱，再加一个 <code>transpose</code> 元素，它“表示记谱音高需要加上多少，才能得到正确的实际音高”。两种方式印出来的五线谱一模一样。如果软件按第二种文件的记谱音高来填六线谱，每个音都会高一个八度。</p>
<p>我们在 MuseScore 4.7.5 里亲眼见过。一个文件里是开放把位的 C 大调音阶，实际音高从 C3 到 C4（从 A 弦第 3 品到 B 弦第 1 品），按第二种方式记谱，并写明了弦和品。MuseScore 把所有品都换掉了，写成从 B 弦第 1 品到 1 弦第 8 品——同一条音阶，高了一个八度。改用带 8 的谱号按实际音高记谱后，同一条音阶打开时，每根弦、每个品都和文件里写的一致。ScoreStarling 下载的 MusicXML 就是这样写六线谱声部的。</p>
<p class="note">如果别人给你的六线谱高了一个八度，先别急着改音，检查一下源文件里这个声部的谱号和移调设置。</p>

<h2 id="capo">变调夹在文件里改变了什么？</h2>
<p>改变的是空弦音。MusicXML 的 <code>capo</code> 元素会按它的半音数“改变各弦的空弦定弦”，而品的编号从空弦的 0 开始，所以变调夹夹在第 2 品时，0 品就是在变调夹处弹响的弦，第 3 品则是从琴枕数起的第 5 品。这和吉他手看带变调夹的弹唱谱时的读法一致。MuseScore 4.7.5 打开我们的 MusicXML 时忽略了变调夹，从琴枕开始标品，所以最好在乐谱的文字说明里也注明变调夹的位置。</p>

<h2 id="scorestarling">ScoreStarling 怎样写六线谱</h2>
<p>从 2026年10月8日起，ScoreStarling 可以把吉他、贝斯或尤克里里声部显示为五线谱加下方的 TAB 谱，或者只显示 TAB 谱，来源可以是录音或视频链接、PDF 乐谱，或者 MusicXML、MIDI、ABC 文件。默认的弦位遵循上文的低品优先规则；“换把最少”会帮你规划，让手尽量停在一个把位；你也可以把任意一个音挪到另一根弦上（音高不变），或者直接输入品数。定弦包括 Drop D、DADGAD 和开放 G，变调夹最高可以夹到第 12 品。任何弦都无法按原样弹出的音会被标出。它不记推弦、滑音、击弦和扫弦，整首曲子也只用一种定弦。可以在<a href="https://scorestarling.com/zh-hans/audio-to-guitar-tab">音频转吉他谱</a>、<a href="https://scorestarling.com/zh-hans/youtube-to-guitar-tab">B 站、YouTube 视频转吉他谱</a>或<a href="https://scorestarling.com/zh-hans/sheet-music-to-guitar-tab">五线谱转吉他谱</a>页面上试试。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://github.com/musescore/MuseScore/blob/master/src/engraving/dom/stringdata.cpp">MuseScore，<code>src/engraving/dom/stringdata.cpp</code></a>：<code>convertPitch</code> 和 <code>fretChords</code>，2026年10月7日查阅。</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/string/">MusicXML 4.0 的 string 元素</a>：1 弦是全长的弦中音最高的一根。</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/fret/">MusicXML 4.0 的 fret 元素</a>：空弦记为 0。</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/capo/">MusicXML 4.0 的 capo 元素</a>.</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/transpose/">MusicXML 4.0 的 transpose 元素</a>.</li>
<li>我们的对比测试：标准定弦下的 480 个随机和弦，MuseScore Studio 4.7.5 命令行对比 ScoreStarling；以及用两种方式记谱的 C 大调音阶。2026年10月8日。</li>
</ol>
</section><h2>常见问题</h2><h3>为什么打谱软件把音排在我不会用的那根弦上？</h3><p>因为五线谱不会标明用哪根弦，软件只能按规则来排。MuseScore 会选能弹出这个音的音最高的那根弦，也就是最低的品；和弦里某个音要用的弦如果已被占用，就顺延到下一根空着的弦。你习惯在别处弹的音，自己改到对应的弦上就行，音高不会变。</p><h3>为什么我的吉他六线谱高了一个八度？</h3><p>很可能是文件用 transpose 元素，把吉他记得比实际音高高一个八度，而软件按记谱音高排了品。改成按实际音高记谱、用下方带 8 的高音谱号，就能解决。在我们的测试中，用第一种方式记谱的开放把位 C 大调音阶，被 MuseScore 4.7.5 读成了高一个八度、排在 1、2 弦上的同一条音阶。</p><h3>夹了变调夹，0 品指的是琴枕还是变调夹？</h3><p>在 MusicXML 里指的是变调夹：capo 元素会把各弦的空弦音升高相应的半音数，所以 0 品就是在变调夹处弹响的弦。MuseScore 4.7.5 打开我们的 MusicXML 时忽略了变调夹，从琴枕开始数品。</p><h3>ScoreStarling 能把录音扒成吉他六线谱吗？</h3><p>能，2026年10月8日起就可以：它会把录音写成五线谱并在下方配上六线谱，也可以只显示六线谱，支持吉他、贝斯和尤克里里，单件乐器免费。它不记推弦、滑音和击弦。详见<a href="https://scorestarling.com/zh-hans/audio-to-guitar-tab">音频转吉他谱</a>。</p>]]></content:encoded>
    </item>
    <item>
      <title>MP3 会影响扒谱准确率吗？WAV 与 MP3 实测对比</title>
      <link>https://scorestarling.com/zh-hans/blog/mp3-vs-wav-music-transcription</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/mp3-vs-wav-music-transcription</guid>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>研究</category>
      <description>基本不会。我们用三个转谱引擎测试了同一批录音的 WAV、MP3 和 AAC 版本：320 kbit/s 的 MP3 让起音 F1 最多只变化 0.002，每个文件的时长也完全不变。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>320 kbit/s 的 MP3 不会。2026年10月5日，我们用三个引擎分别转了同一批录音的无损版本，以及经过 MP3 和 AAC 编码再解码的版本：Spotify 的 Basic Pitch 处理 12 段吉他录音，一个钢琴独奏模型处理 50 段钢琴演奏，Mirelo 处理一首乐队歌曲。320 kbit/s 的 MP3 让每个引擎的起音 F1（取值 0 到 1）最多只变化 0.002，相当于几千个音符里只变了寥寥几个，而且每个文件的长度精确到采样点、分毫不差。192 kbit/s 的 MP3 和 256 kbit/s 的 AAC 让分数最多变化 0.008，升高和降低的次数差不多；AAC 文件解码后最多长出 733 个采样点。</p>
</section>

<h2 id="why-ask">MP3 为什么会改变音符？</h2>
<p>MP3 和 AAC 会丢掉人耳不太容易察觉的细节，主要是紧挨着较响声音的弱音和高频部分。Basic Pitch 这类模型读取的是音频的时频图，谁也不能保证它们会像听众一样忽略这些细节。一个处在模型阈值附近的音符，可能因为频谱的细微变化而出现或消失。</p>
<p>已有研究表明影响很小。Urbano 等人发现，码率从约 160 kbit/s 起，MFCC 特征在有损编码下保持稳定；而反映音级的色度（chroma）特征几乎完全不受编解码器和码率影响（ISMIR 2014）。他们测的是特征，不是转谱得到的音符，所以我们测了音符。</p>
<p>我们也有实际的考虑。录音及其播放音频的无损副本占了乐谱存储空间的 77%，我们想改存更小的副本，但前提是准确率丝毫不受影响。</p>

<h2 id="how-we-tested">我们怎么测的</h2>
<p>每段录音都用 FFmpeg 8.1.1 分别编码（LAME 用 320 或 192 kbit/s 的恒定码率，FFmpeg 自带的 AAC 编码器用 256 kbit/s），再用读取上传文件的同一套代码解码。对照组把无损音频改动 0.01 dB——谁也听不出来——用来显示完全不经过编解码器时，各项指标本身会浮动多少。三个引擎都是确定性的：相同的输入总是得到相同的音符。</p>
<div class="table-scroll" role="region" aria-label="每个引擎的测试内容" tabindex="0">
<table>
<caption>引擎与录音</caption>
<thead>
<tr><th scope="col">引擎</th><th scope="col">录音</th><th scope="col">参考标注</th></tr>
</thead>
<tbody>
<tr><th scope="row">Basic Pitch（ScoreStarling 处理单件乐器时的用法）</th><td>12 段 GuitarSet 录音（六位演奏者各一段独奏、一段伴奏），Slakh2100 中一首乐队歌曲的前 60 秒，以及一首我们自己创作并合成的 58 秒乐队作品</td><td>数据集自带的标注</td></tr>
<tr><th scope="row">Kong 等人的钢琴独奏模型</th><td>25 首钢琴曲（24 首公有领域乐谱，外加 1 首我们自己写的），每首以不同的弹性速度演奏两遍，并用 SoundFont 钢琴音色加延音踏板渲染：共 50 段演奏</td><td>实际演奏的音符</td></tr>
<tr><th scope="row">Mirelo 的 Audio-to-MIDI（a2m-1.1）</th><td>同一首 Slakh 乐队歌曲的 60 秒片段，只测了无损和 MP3 320</td><td>这首歌的 MIDI</td></tr>
</tbody>
</table>
</div>
<p>起音 F1（onset F1）的判定方法是：转出的音符与参考标注中某个音符的音高相差不超过 50 音分、起点相差不超过 50 毫秒，就算转对；漏掉的音和多出来的音扣分相同。1.0 为满分。《<a href="https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy">AI 扒谱到底有多准？我们实测了一下</a>》一文对这个指标有完整说明。</p>

<h2 id="basic-pitch-results">单件乐器的结果变了吗？</h2>
<p>几乎没变。在 12 段吉他录音上，Basic Pitch 的起音 F1 在每种格式下都保持在 0.761 到 0.764 之间，而立体声文件缩小到了 WAV 的 14% 到 23%。</p>
<figure class="bars-figure">
<div class="bars" aria-hidden="true">
<p class="bars-title">起音 F1，12 段吉他录音 <span>0 到 1，越高越好</span></p>
<div class="bar-row"><span>无损</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="76.26%" height="10" rx="3"/></svg><b>0.763</b></div>
<div class="bar-row"><span>对照</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="76.27%" height="10" rx="3"/></svg><b>0.763</b></div>
<div class="bar-row"><span>MP3 320</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="76.11%" height="10" rx="3"/></svg><b>0.761</b></div>
<div class="bar-row"><span>AAC 256</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="76.44%" height="10" rx="3"/></svg><b>0.764</b></div>
<div class="bar-row"><span>MP3 192</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="76.16%" height="10" rx="3"/></svg><b>0.762</b></div>
</div>
<div class="bars" aria-hidden="true">
<p class="bars-title">立体声录音的文件大小 <span>占 WAV 的比例</span></p>
<div class="bar-row"><span>无损</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="100%" height="10" rx="3"/></svg><b>100%</b></div>
<div class="bar-row"><span>对照</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="100%" height="10" rx="3"/></svg><b>100%</b></div>
<div class="bar-row"><span>MP3 320</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="23%" height="10" rx="3"/></svg><b>23%</b></div>
<div class="bar-row"><span>AAC 256</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="17%" height="10" rx="3"/></svg><b>17%</b></div>
<div class="bar-row"><span>MP3 192</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="14%" height="10" rx="3"/></svg><b>14%</b></div>
</div>
<figcaption>同样 12 段 GuitarSet 录音的五种格式：文件缩小到四分之一甚至更小，起音 F1 却几乎没动。具体数值见下表。</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Basic Pitch 在各格式下的结果" tabindex="0">
<table>
<caption>ScoreStarling 所用 Basic Pitch 在各格式下的结果（2026年10月5日）</caption>
<thead>
<tr><th scope="col">指标</th><th scope="col" class="num">无损</th><th scope="col" class="num">对照</th><th scope="col" class="num">MP3 320</th><th scope="col" class="num">AAC 256</th><th scope="col" class="num">MP3 192</th></tr>
</thead>
<tbody>
<tr><th scope="row">起音 F1，吉他</th><td class="num">0.7626</td><td class="num">0.7627</td><td class="num">0.7611</td><td class="num">0.7644</td><td class="num">0.7616</td></tr>
<tr><th scope="row">起止 F1，吉他</th><td class="num">0.5224</td><td class="num">0.5222</td><td class="num">0.5212</td><td class="num">0.5228</td><td class="num">0.5210</td></tr>
<tr><th scope="row">起音 F1，Slakh 歌曲</th><td class="num">0.5204</td><td class="num">0.5210</td><td class="num">0.5204</td><td class="num">0.5240</td><td class="num">0.5227</td></tr>
<tr><th scope="row">起音 F1，我们的乐队作品</th><td class="num">0.7136</td><td class="num">0.7136</td><td class="num">0.7123</td><td class="num">0.7089</td><td class="num">0.7054</td></tr>
<tr><th scope="row">找到的音符（共 3,893 个）</th><td class="num">2,741</td><td class="num">2,742</td><td class="num">2,739</td><td class="num">2,744</td><td class="num">2,737</td></tr>
<tr><th scope="row">其中有变化的音符</th><td class="num">—</td><td class="num">1</td><td class="num">10</td><td class="num">17</td><td class="num">14</td></tr>
<tr><th scope="row">调与标注一致</th><td class="num">12 段中 10 段</td><td class="num">12 段中 10 段</td><td class="num">12 段中 10 段</td><td class="num">12 段中 10 段</td><td class="num">12 段中 10 段</td></tr>
<tr><th scope="row">速度不变</th><td class="num">—</td><td class="num">14 段中 14 段</td><td class="num">14 段中 14 段</td><td class="num">14 段中 14 段</td><td class="num">14 段中 14 段</td></tr>
<tr><th scope="row">长度变化（采样点）</th><td class="num">—</td><td class="num">0</td><td class="num">0</td><td class="num">+16 到 +733</td><td class="num">0</td></tr>
<tr><th scope="row">信噪比，dB（中位数）</th><td class="num">—</td><td class="num">58.4</td><td class="num">53.2</td><td class="num">39.4</td><td class="num">30.3</td></tr>
<tr><th scope="row">文件大小，单声道 / 立体声</th><td class="num">100%</td><td class="num">100%</td><td class="num">45% / 23%</td><td class="num">23% / 17%</td><td class="num">27% / 14%</td></tr>
</tbody>
</table>
</div>
<p class="note">无损版本的分数是 0.763，而不是我们 10月3日那篇文章中的 0.744：从 10月4日起，ScoreStarling 把单件乐器写成一条旋律线，这改变了其中五段独奏的结果。两次测试用的都是 Basic Pitch 0.4.0。</p>

<h2 id="piano-results">钢琴独奏呢？</h2>
<p>钢琴模型在每种格式下听出的音符都一样。以实际演奏的音符为参照，它的起音 F1 在无损版本上为 0.9427，对照组为 0.9426，MP3 320 为 0.9428，MP3 192 为 0.9433；每种格式下都找到了约 92.2% 的谱面音符。</p>
<p>根据这些音符判断拍子和小节线的那一步就没那么稳定了，但原因不在编解码器。经过人耳听不出的对照改动后，50 段演奏里有 5 段的小节线位置变了；经过 MP3 320 后有 7 段，其中 4 段和对照组是同样的演奏。这一步对任何改动都很敏感，所以我们把它看作测量本身的噪声，而不是 MP3 的代价。钢琴没有测试 AAC。</p>

<h2 id="band-results">整支乐队呢？</h2>
<p>我们用 Mirelo 做了一次付费对比，测的是那首 60 秒的乐队歌曲：无损文件和 MP3 都返回了 842 个音符和鼓点，其中 829 个（98.5%）的起点与无损版本相差不到 50 毫秒。</p>
<div class="table-scroll" role="region" aria-label="Mirelo 的无损与 MP3 结果" tabindex="0">
<table>
<caption>Mirelo Audio-to-MIDI 处理一首 60 秒乐队歌曲的结果</caption>
<thead>
<tr><th scope="col">指标</th><th scope="col" class="num">无损</th><th scope="col" class="num">MP3 320</th></tr>
</thead>
<tbody>
<tr><th scope="row">起音 F1，有音高的音符（全曲 703 个）</th><td class="num">0.6707</td><td class="num">0.6687</td></tr>
<tr><th scope="row">鼓的起音 F1（423 个鼓点）</th><td class="num">0.6983</td><td class="num">0.7023</td></tr>
<tr><th scope="row">贝斯 / 吉他 / 钢琴的起音 F1</th><td class="num">0.455 / 0.681 / 0.827</td><td class="num">0.471 / 0.678 / 0.832</td></tr>
<tr><th scope="row">速度，BPM</th><td class="num">133.0</td><td class="num">133.0</td></tr>
</tbody>
</table>
</div>

<h2 id="why-notes-moved">为什么还是有音符变了？</h2>
<p>因为输入只要有任何变化，就会牵动少数正好卡在模型阈值上的音符。在 Basic Pitch 找到的约 2,740 个音符中，听不出差别的对照改动了 1 个，MP3 320 改动了 10 个，AAC 改动了 17 个，MP3 192 改动了 14 个，单段录音里最多不超过 7 个。五组对比中，MP3 320 有三组略低（低 0.0013 到 0.0020），一组持平，一组略高。AAC 和 MP3 192 在吉他或 Slakh 歌曲上有所上升，在我们合成的乐队作品上有所下降，幅度最多 0.008。如果某种编解码器真的损害了准确率，应该会在所有测试上都明显下降，但没有一种是这样。</p>
<p>编解码器确实会改变的，是首尾的时间。AAC 编码器会在开头加入静音的预置采样，并把结尾补齐到完整的一帧；Apple 的文档写明延迟为 2112 个采样点。我们的 AAC 文件解码后长了 16 到 733 个采样点。FFmpeg 写出的 MP3 带有记录编码器延迟的 Xing/LAME 头信息，我们的 MP3 文件解码后与原始长度完全一致。当乐谱要和录音同步播放时，这一点很重要。</p>

<h2 id="which-file">该上传哪种文件？</h2>
<ul>
<li>手头有什么就传什么。先把 WAV 转成 MP3 得不到任何好处，把 MP3 转回 WAV 也恢复不了任何东西。</li>
<li>如果一定要压缩，320 kbit/s 的 MP3 在信噪比上最接近原始文件，而且长度完全不变。</li>
<li>低码率没有测过。我们只测到 192 kbit/s，所以请留意你的录音 App 保存的是什么格式和码率。</li>
<li>录音质量远比格式重要：一个清楚的声部，混响少，没有背景音乐。要检查哪些地方，见《<a href="https://scorestarling.com/zh-hans/blog/voice-memo-to-sheet-music">如何把语音备忘录转成乐谱</a>》。</li>
</ul>
<p>ScoreStarling 已据此做了调整：从 2026年10月5日起，上传的 WAV、AIFF 或 FLAC 文件会保存为 320 kbit/s 的 MP3，并用这份副本转谱，立体声录音的存储空间因此降到 WAV 的 23%。MP3、M4A、Ogg 和视频文件按原样保存。</p>

<h2 id="limits">这次测试的局限</h2>
<ul>
<li>Mirelo 只测了一首乐队歌曲；钢琴用的是合成音频，不是真实录制的钢琴。</li>
<li>没有测人声。我们手上的真实录音是私人录音，而且本来就是 AAC 文件，没法作为无损参照。</li>
<li>每种格式只用了一个编码器，也没有测低于 192 kbit/s 的码率。</li>
<li>起音 F1 只说明音符有没有被听出来，不说明写出的乐谱好不好读。</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://archives.ismir.net/ismir2014/paper/000326.pdf">What Is the Effect of Audio Quality on the Robustness of MFCCs and Chroma Features?</a>——Urbano、Bogdanov、Herrera、Gómez 和 Serra，ISMIR 2014</li>
<li><a href="https://zenodo.org/records/3371780">GuitarSet 1.1.0</a>——Xi、Bittner、Pauwels、Ye 和 Bello，Zenodo（CC BY 4.0）</li>
<li><a href="https://zenodo.org/records/4599666">Slakh2100</a>——Manilow、Wichern、Seetharaman 和 Le Roux，Zenodo（CC BY 4.0）</li>
<li><a href="https://arxiv.org/abs/2203.09893">A Lightweight Instrument-Agnostic Model for Polyphonic Note Transcription and Multipitch Estimation</a>——Bittner 等，Spotify，2022（Basic Pitch）</li>
<li><a href="https://arxiv.org/abs/2010.01815">High-resolution Piano Transcription with Pedals by Regressing Onset and Offset Times</a>——Kong、Li、Song、Wan 和 Wang，2020</li>
<li><a href="https://mir-eval.readthedocs.io/latest/api/transcription.html">mir_eval.transcription</a>——mir_eval 文档</li>
<li><a href="https://ffmpeg.org/ffmpeg-formats.html#mp3">FFmpeg formats: the mp3 muxer</a>——FFmpeg 文档</li>
<li><a href="https://developer.apple.com/library/archive/technotes/tn2258/_index.html">TN2258: AAC Audio, Encoder Delay and Synchronization</a>——Apple</li>
</ol>
</section><h2>常见问题</h2><h3>扒谱该上传 WAV 还是 MP3？</h3><p>都可以。在我们 2026年10月5日的测试中，用三个不同的引擎转谱，320 kbit/s 的 MP3 与其来源 WAV 的起音 F1 相差都不超过 0.002。录音设备或编辑软件给你什么文件，就直接上传，不用转换格式。192 kbit/s 的 MP3 和 256 kbit/s 的 AAC 相差在 0.008 以内；更低的码率没有测试。</p><h3>MP3 码率更低，音符会变吗？</h3><p>会变几个，有升有降。在 14 段录音约 2,740 个匹配上的音符中，192 kbit/s 时有 14 个发生了变化，320 kbit/s 时有 10 个，而一次人耳听不出的音量改动也让 1 个变了。起音 F1 在 192 kbit/s 时最多变化 0.008，在 320 kbit/s 时最多变化 0.002。低于 192 kbit/s 的码率没有测量。</p><h3>为什么 AAC 文件会比 WAV 稍长一点？</h3><p>AAC 编码器会在开头加入一段静音的预置采样（priming samples），并把结尾补齐到完整的一帧；Apple 的文档写明预置延迟为 2112 个采样点。如果播放器或解码器没有把它们裁掉，音频就会稍长一些。在我们的测试中，AAC 文件解码后长了 16 到 733 个采样点。FFmpeg 写出的、带 Xing/LAME 头信息的 MP3，解码后长度完全一致。</p><h3>ScoreStarling 会把我的 WAV 转成 MP3 吗？</h3><p>会。从 2026年10月5日起，上传的 WAV、AIFF 或 FLAC 文件会保存为 320 kbit/s 的 MP3（也就是本文测试的设置），并用这份副本转谱。上传的 MP3、M4A、Ogg 和视频则按原样保存。</p>]]></content:encoded>
    </item>
    <item>
      <title>手机版 ChatGPT 里的 MCP App：我们的适配经验</title>
      <link>https://scorestarling.com/zh-hans/blog/mcp-app-chatgpt-mobile</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/mcp-app-chatgpt-mobile</guid>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>技术</category>
      <description>在手机上，ChatGPT 把输入框算进了约 127 px 的底部边距，还会用旧结果重建我们的 MCP App。本文记录四个 bug、对应的修复，以及能抓住它们的测试。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>在手机上，ChatGPT 会把标题栏和输入框叠在全屏 MCP App 上面，并以安全区域边距的形式上报，我们实测分别约为 47 px 和 127 px；应用打开期间，底部边距还会变化。每次进入或退出全屏，它还会用对话中最初的工具结果重建我们的面板。应对办法是：把边距区域留空，但不要再加自己的余量；每次上下文变化都重新定位用脚本定位的元素；加载时检查结果是否已经过时。我们测试的是 iPhone 模拟器里的 ChatGPT 手机网页版，不是原生 App。</p>
</section>

<h2 id="what-broke-on-a-phone">在手机版 ChatGPT 里打开我们的 MCP App，出了哪些问题？</h2>
<p>四个问题，都和全屏有关。ScoreStarling 的面板会在对话中显示乐谱（<a href="https://scorestarling.com/zh-hans/mcp">连接方法</a>）；全屏时可以点选音符，再从选区卡片就这些音符向对话提问，手机上这张卡片停靠在播放器上方。2026年10月7日，我们在 iOS 模拟器的 iPhone 17（402 × 874 点）上，用 Safari 打开 ChatGPT 手机网页版来运行它。面板所在的 iframe 是跨域的，所以我们只能从截图中读取它的状态。</p>
<div class="table-scroll" role="region" aria-label="在手机版 ChatGPT 上发现的问题" tabindex="0">
<table>
<caption>在 ChatGPT 手机网页版上发现的问题（2026年10月7日）</caption>
<thead><tr><th scope="col">现象</th><th scope="col">原因</th><th scope="col">修复</th></tr></thead>
<tbody>
<tr><th scope="row">播放器浮到了屏幕约三分之一高的位置</th><td>底部边距（约 127 px）已经覆盖了输入框，我们又在它上面固定多留了 96 px</td><td>取边距和 96 px 中的较大值</td></tr>
<tr><th scope="row">草稿变长或出现 Thinking 状态栏时，播放器上移了，停靠的卡片却没动</th><td>ChatGPT 改变了边距；播放器靠 CSS 跟随，卡片却由脚本定位</td><td>每次上下文变化都重新定位卡片</td></tr>
<tr><th scope="row">编辑后进入或退出全屏，显示的是旧乐谱，而且只读</th><td>ChatGPT 用最初的工具结果新建了面板</td><td>结果落后时读取当前版本</td></tr>
<tr><th scope="row">在对话中编辑后，卡片悬在播放器上方约 40 px 处</th><td>我们的“Updated from the chat”提示挪动了卡片定位所参照的区域</td><td>该区域尺寸变化时重新定位卡片</td></tr>
</tbody>
</table>
</div>

<h2 id="message-box-safe-area">手机上，全屏 MCP App 为什么会被 ChatGPT 的输入框挡住？</h2>
<p>因为 ChatGPT 把输入框画在你的 iframe 上面，并通过底部安全区域边距告诉你它挡住了多少。忽略这个边距，你的控件就会被输入框压住；像我们那样在上面再加一段自己的余量，控件又会悬在半空。MCP Apps 规范对 <code>safeAreaInsets</code> 的描述只有一句“Safe area boundaries in pixels”（以像素为单位的安全区域边界），OpenAI 的文档说全屏时输入框（composer）仍会叠在上面，但没有给出尺寸（两者均于 2026年10月7日查看）。我们给出的 47 px 和 127 px 是根据截图估算的。</p>
<p>10月6日，手机上的一次对话显示我们的播放器被 ChatGPT 的标题栏挡住了，于是我们把顶栏移到顶部边距以下，把播放器挪到底部；在底部，我们的 CSS 还在底部边距之上另外为输入框留了 96 px。本地用 34 px 边距测试时看起来没问题，换成 ChatGPT 的 127 + 96 就不对了。现在面板取两者中的较大值，同时也会读取 ChatGPT 的 <code>window.openai.safeArea</code>：</p>
<pre><code>// panel.js, simplified: the host's inset or 96 px, never both
const bottom = Math.max(context.safeAreaInsets?.bottom || 0, window.openai?.safeArea?.insets?.bottom || 0);
root.style.setProperty('--safe-bottom', `${bottom}px`);
root.style.setProperty('--composer-space', fullscreen ? `${Math.max(0, 96 - bottom)}px` : '0px');
/* panel.css */
.stage &gt; .player { bottom: calc(10px + var(--safe-bottom) + var(--composer-space)); }</code></pre>
<figure class="diagram">
<svg viewBox="0 0 320 356" aria-hidden="true" focusable="false">
<rect class="d-box" x="10" y="8" width="140" height="304" rx="12"/>
<path class="d-line" d="M10 40.5H150"/>
<path class="d-line" d="M20 52H140M20 55H140M20 58H140M20 61H140M20 64H140"/>
<path class="d-line" d="M20 82H140M20 85H140M20 88H140M20 91H140M20 94H140"/>
<path class="d-line" d="M20 112H140M20 115H140M20 118H140M20 121H140M20 124H140"/>
<path class="d-line" d="M20 142H140M20 145H140M20 148H140M20 151H140M20 154H140"/>
<path class="d-line" d="M20 172H140M20 175H140M20 178H140M20 181H140M20 184H140"/>
<rect class="d-box d-accent d-dash" x="13" y="234.4" width="134" height="33.4" rx="4"/>
<text class="d-small d-mark" x="80" y="255" text-anchor="middle">+ 96 px</text>
<rect class="d-box" x="13" y="200.9" width="134" height="30" rx="9"/>
<circle class="d-mark" cx="28" cy="215.9" r="7"/>
<path class="d-line" d="M42 215.9H138"/>
<rect class="d-mark" x="42" y="214.9" width="28" height="2" rx="1"/>
<path class="d-shade" d="M10 20A12 12 0 0 1 22 8H138A12 12 0 0 1 150 20V24.4H10Z"/>
<text class="d-small" x="80" y="20" text-anchor="middle">标题栏 · 47 px</text>
<path class="d-shade" d="M10 267.8H150V300A12 12 0 0 1 138 312H22A12 12 0 0 1 10 300Z"/>
<rect class="d-box" x="18" y="273" width="124" height="18" rx="9"/>
<text class="d-small" x="27" y="285.8">Ask ScoreStarling</text>
<text class="d-small" x="80" y="305" text-anchor="middle">输入框 · 127 px</text>
<rect class="d-frame" x="10" y="8" width="140" height="304" rx="12"/>
<text class="d-text" x="80" y="334" text-anchor="middle">修复前</text>
<text class="d-small" x="80" y="349" text-anchor="middle">留空 127 + 96 px</text>
<rect class="d-box" x="170" y="8" width="140" height="304" rx="12"/>
<path class="d-line" d="M170 40.5H310"/>
<path class="d-line" d="M180 52H300M180 55H300M180 58H300M180 61H300M180 64H300"/>
<path class="d-line" d="M180 82H300M180 85H300M180 88H300M180 91H300M180 94H300"/>
<path class="d-line" d="M180 112H300M180 115H300M180 118H300M180 121H300M180 124H300"/>
<path class="d-line" d="M180 142H300M180 145H300M180 148H300M180 151H300M180 154H300"/>
<path class="d-line" d="M180 172H300M180 175H300M180 178H300M180 181H300M180 184H300"/>
<path class="d-line" d="M180 202H300M180 205H300M180 208H300M180 211H300M180 214H300"/>
<rect class="d-box" x="173" y="234.3" width="134" height="30" rx="9"/>
<circle class="d-mark" cx="188" cy="249.3" r="7"/>
<path class="d-line" d="M202 249.3H298"/>
<rect class="d-mark" x="202" y="248.3" width="28" height="2" rx="1"/>
<path class="d-shade" d="M170 20A12 12 0 0 1 182 8H298A12 12 0 0 1 310 20V24.4H170Z"/>
<text class="d-small" x="240" y="20" text-anchor="middle">标题栏 · 47 px</text>
<path class="d-shade" d="M170 267.8H310V300A12 12 0 0 1 298 312H182A12 12 0 0 1 170 300Z"/>
<rect class="d-box" x="178" y="273" width="124" height="18" rx="9"/>
<text class="d-small" x="187" y="285.8">Ask ScoreStarling</text>
<text class="d-small" x="240" y="305" text-anchor="middle">输入框 · 127 px</text>
<rect class="d-frame" x="170" y="8" width="140" height="304" rx="12"/>
<text class="d-text" x="240" y="334" text-anchor="middle">修复后</text>
<text class="d-small" x="240" y="349" text-anchor="middle">留空 127 px</text>
</svg>
<figcaption>我们的面板在 402 × 874 点的手机上（大致按比例），上方是 ChatGPT 的标题栏（约 47 px），下方是输入框（约 127 px）。修复前，面板在边距之上又多留了 96 px；现在只留出边距，宿主上报的边距小于 96 px 时才留 96 px。</figcaption>
</figure>

<h2 id="host-context-changed">手机上全屏 MCP App 打开期间，会有什么变化？</h2>
<p>边距会变。我们实测时，草稿增加到两行、Thinking 状态栏取代输入框时，底边都会移动。规范允许宿主在任何上下文字段变化时发送 <code>ui/notifications/host-context-changed</code>，只带变化了的字段，由视图自行合并。</p>
<p>我们的播放器位置绑定在一个 CSS 变量上，跟着变了。选区卡片由脚本定位，停在了原地，所以现在每次上下文变化都会重新定位它。我们自己的提示又造成了第二种错位：“Updated from the chat”会在乐谱上方显示 20 秒，把乐谱区域往下推。卡片的位置是从这个区域的顶部算起的，于是滑到了播放器上面；如果这期间有别的操作重新定位了它，提示消失后它又会悬在播放器上方。现在我们给这个区域加了一个 <code>ResizeObserver</code>，同样会重新定位卡片，每个动画帧最多一次：</p>
<pre><code>this.context = {...this.context, ...params};  // host.js, simplified: merge the partial update
function hostContext(next) { applyContext(next); editor.queuePlace(); }
new ResizeObserver(() =&gt; { updateClip(); editor.queuePlace(); }).observe($('stage'));</code></pre>

<h2 id="rebuilt-from-old-result">退出全屏后，MCP App 为什么显示的是旧版本？</h2>
<p>因为手机上的 ChatGPT 每次进入或退出全屏都会新建一个面板，交给它的是对话中最初的工具结果，那是在任何编辑之前生成的。规范允许宿主随时销毁视图；而工具结果只记录了工具运行那一刻的状态。</p>
<p>我们的工具结果一半是快照、一半是实时数据：页面图片和音符对应它自己的修订版本，另有一个“当前修订版本”字段，在面板获取结果时才读取。重建后的面板画出的是修订版本 0，发现乐谱已经到了修订版本 3，于是变成只读。它检测对话编辑时，拿服务器的修订版本去和那个本来就是最新的字段比较，所以永远追不上。现在，如果宿主交给面板的结果已经落后于乐谱，面板会读取一次当前修订版本；只有一种例外：结果里带着等待批准的修改建议，这种结果本来就应该与当前版本不同。</p>

<h2 id="test-without-a-phone">不用手机，怎么测试 ChatGPT 在手机上的行为？</h2>
<p>在仿真环境里模仿真实宿主，并证明每个测试在去掉修复后都会失败。我们用的是 sunpeak，它为 Playwright 测试仿制了 ChatGPT 和 Claude。它的手机版 ChatGPT 界面（0.20.91 版）会画出标题栏和输入框，并把输入框上报为 92 px 的边距。我们为这几个 bug 写的三个测试运行在 402 × 874 的触屏上，补上了其余部分：它们通过这个界面的沙箱 iframe（它和 ChatGPT 的 iframe 一样，是面板的父级）依次发送 127、160、70 和 127 px 的新边距；编辑之后重新加载面板，并把最初的工具结果交给它。另一个测试像对话那样，在面板之外编辑乐谱。播放器最终必须位于“边距与 96 px 中的较大者”上方 0–24 px 处，卡片必须位于播放器上方 0–16 px 处。</p>
<figure class="flow-figure">
<ol class="flow-steps">
<li><b>运行真实宿主</b><span>在模拟器里打开 ChatGPT 手机网页版</span></li>
<li><b>修复一个原因</b><span>部署，刷新应用的工具，再看一遍</span></li>
<li><b>模仿宿主</b><span>从仿真环境的 iframe 发出宿主的消息</span></li>
<li><b>撤掉修复</b><span>没有这项修复，新测试必须失败</span></li>
</ol>
<figcaption>手机上的发现如何变成本地测试。</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="逐一撤掉修复后的仿真测试" tabindex="0">
<table>
<caption>逐一撤掉各项修复的结果（2026年10月7日）</caption>
<thead><tr><th scope="col">撤掉的修复</th><th scope="col">失败的检查</th></tr></thead>
<tbody>
<tr><th scope="row">取边距或 96 px，不再两者相加</th><td>输入框测试：“播放器紧贴输入框”</td></tr>
<tr><th scope="row">上下文变化时重新定位卡片</th><td>输入框测试：“卡片不遮挡播放器”</td></tr>
<tr><th scope="row">重建的面板读取当前版本</th><td>重建测试：撤销按钮一直不可用</td></tr>
<tr><th scope="row">区域尺寸变化时重新定位卡片</th><td>提示测试：“卡片停靠在播放器上”</td></tr>
</tbody>
</table>
</div>
<p>这三个测试跑一遍用时 59.9 秒。同一天又加了第四个测试，检查手机编辑器的单行顶栏和播放器；我们的贡献者规则现在要求，只要改动了全屏布局、边距、卡片或结果处理，就必须跑手机测试。这次手机实测还发现：对于“Change this note to C”（把这个音改成 C），ChatGPT 把选中的 B4 改成了低七度的 C4；我们在工具描述里加了一条“取最近八度”的规则后，用真实模型跑的本地测试从 3 次通过 2 次提升到 6 次全部通过。</p>

<h2 id="not-verified">还有哪些没验证</h2>
<p>我们的手机测试都是在 iOS 模拟器和 ChatGPT 手机网页版上做的：没有用真机，也没有测原生 App。尚待确认的有：</p>
<ul>
<li>ChatGPT 实际发送的边距数值和发送时机；我们的数值是估算的，仿真环境沿用了这些估算；</li>
<li>Safari 展开的工具栏，会挡住一半输入框和播放器的下沿；</li>
<li>键盘收起后，Safari 留下了约 156 点的页面滚动，跨域的面板无法把它复原；</li>
<li>在 ChatGPT 还没回答完时打开全屏，会出现一条最高约 777 点的不透明色带；</li>
<li>ChatGPT 的底部回复面板，以及它自己销毁 iframe 的行为，仿真环境都没有模仿；</li>
<li>电脑上的 ChatGPT：全屏是一个侧边面板，输入框在面板之外；我们没有读取它的边距。</li>
</ul>

<h2 id="mcp-app-phone-checklist">手机端 MCP App 检查清单</h2>
<ul>
<li>上下两个边距都要留空；如果你还为输入框留了兜底空间，取两者中的较大值，绝不要相加。</li>
<li>合并每一条 <code>host-context-changed</code> 通知，并重新定位所有用脚本定位的元素，每帧最多一次。</li>
<li>监听你自己的容器，留意会挪动它们的提示。</li>
<li>要预料到宿主会用旧的工具结果重建视图；加载时把结果的版本和服务器上的版本比一比。</li>
<li>给本地测试宿主设置在真实宿主上测得的边距。</li>
<li>按 OpenAI 文档的建议，把 UI 资源 URI 当作缓存键；发布新的 URI 后，在 ChatGPT 里刷新该应用的工具（在插件设置中依次点 Manage app、Refresh tools；2026年10月7日核实）。</li>
<li>把在真实宿主上的每个发现都变成一个仿真测试，并确保没有修复时它会失败。</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx">SEP-1865：MCP Apps——MCP 的交互式用户界面（稳定版，2026-01-26）</a> — Model Context Protocol</li>
<li><a href="https://developers.openai.com/plugins/build/chatgpt-ui">为你的 MCP 服务器添加 UI</a> — OpenAI</li>
<li><a href="https://developers.openai.com/plugins/concepts/ui-guidelines">UI 设计指南</a> — OpenAI</li>
<li><a href="https://developers.openai.com/plugins/reference">参考文档</a>（<code>window.openai</code> 组件桥接接口）— OpenAI</li>
<li><a href="https://sunpeak.ai/docs/mcp-testing">MCP 测试框架</a> — sunpeak</li>
</ol>
</section><h2>常见问题</h2><h3>在手机上，ChatGPT 会把输入框算进 safeAreaInsets 吗？</h3><p>我们实测是会的。在 ChatGPT 手机网页版上（iPhone 17 模拟器，2026年10月7日），底部边距约 127 px，覆盖了输入框；顶部边距约 47 px，覆盖了标题栏。MCP Apps 规范没有规定边距里包含什么，所以把上报的区域留空就好，不要在上面再加自己的余量。</p><h3>全屏应用打开期间，ChatGPT 会改变边距吗？</h3><p>我们实测会：草稿变成两行，或者出现 ChatGPT 的 Thinking 状态栏，都会让底边移动。每次收到 <code>ui/notifications/host-context-changed</code> 都要应用新的边距，把通知里带的字段合并进已有的值，并重新定位所有用脚本定位的元素。</p><h3>为什么在手机上切换到全屏后，MCP App 显示的是旧数据？</h3><p>手机上的 ChatGPT 每次进入或退出全屏，都会用对话中最初的工具结果新建一个面板，所以我们的面板显示的是后续编辑之前的乐谱。视图加载时，把结果中的版本和服务器上的版本比较一下；如果结果落后了，就读取一次当前状态。</p><h3>不用手机，能测试 ChatGPT 在手机上的布局吗？</h3><p>能测一部分。我们用 Playwright 在 sunpeak 仿制的手机版 ChatGPT 界面里重现手机上看到的情况：边距变化、面板重建、从对话中发起的编辑；撤掉对应的修复，每个测试都会失败。ChatGPT 实际发送的数值、Safari 的工具栏、键盘、ChatGPT 的底部回复面板和原生 App，仍然需要真机测试。</p>]]></content:encoded>
    </item>
    <item>
      <title>在 Railway 上按 Postgres 队列自动扩缩容 worker</title>
      <link>https://scorestarling.com/zh-hans/blog/autoscale-workers-postgres-queue</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/autoscale-workers-postgres-queue</guid>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>技术</category>
      <description>我们把转谱交给 1–4 个 Railway worker，共用一张 Postgres 任务表：任务认领、120 秒租约、按队列深度扩缩容，以及一个 API bug。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>2026年10月7日，我们把 ScoreStarling 原本唯一的 Railway 服务一分为二：网站只处理请求，1 到 4 个 worker 副本负责转谱。队列仍然是 PostgreSQL 数据库中的一张表：worker 在事务级咨询锁的保护下认领最早的任务，每 15 秒汇报一次；任何任务沉默两分钟，就会被另一个 worker 收回。Railway 不会自己增加副本，所以由担任 leader 的 worker 根据队列情况，通过 Railway API 设置副本数；不过第一个版本查询了一个 API 里并不存在的字段。</p>
</section>

<h2 id="why-separate-workers">为什么要把转谱从 Web 服务器上拆出去？</h2>
<p>因为 Web 服务器同时也是唯一的转谱 worker，想扩容只能换更大的容器。10月7日之前，一个 2 vCPU、8 GB 的 Railway 副本既承载网站、API 和 MCP 端点，又要一个接一个地运行所有转谱任务。只有持有会话级咨询锁的那个实例才处理队列，所以再加一个副本也不会增加任何转谱能力。</p>
<p>现在，同一个镜像靠一项角色配置，作为两个 Railway 服务运行：<code>web</code> 从不接任务，<code>worker</code> 没有公网地址。一个任务就是一次转谱（ScoreStarling 能<a href="https://scorestarling.com/zh-hans/how-it-works">把录音变成乐谱</a>）：在我们抽样的 10月2日至7日生产环境运行中，每个任务占用 worker 17 到 266 秒。</p>
<figure class="diagram">
<svg viewBox="0 0 320 290" aria-hidden="true" focusable="false">
<rect class="d-box" x="80" y="6" width="160" height="46" rx="10"/>
<text class="d-text" x="160" y="26" text-anchor="middle">网站</text>
<text class="d-small" x="160" y="42" text-anchor="middle">页面、上传、MCP</text>
<line class="d-line" x1="160" y1="52" x2="160" y2="82"/>
<line class="d-line" x1="155" y1="76" x2="160" y2="82"/>
<line class="d-line" x1="165" y1="76" x2="160" y2="82"/>
<text class="d-small" x="170" y="71">写入任务</text>
<rect class="d-shade" x="40" y="84" width="240" height="48" rx="10"/>
<text class="d-text" x="160" y="104" text-anchor="middle">PostgreSQL 任务表</text>
<text class="d-small" x="160" y="121" text-anchor="middle">即队列，记录归属与汇报</text>
<line class="d-line" x1="52" y1="162" x2="52" y2="134"/>
<line class="d-line" x1="47" y1="140" x2="52" y2="134"/>
<line class="d-line" x1="57" y1="140" x2="52" y2="134"/>
<line class="d-line d-dash" x1="124" y1="162" x2="124" y2="134"/>
<line class="d-line" x1="119" y1="140" x2="124" y2="134"/>
<line class="d-line" x1="129" y1="140" x2="124" y2="134"/>
<line class="d-line d-dash" x1="196" y1="162" x2="196" y2="134"/>
<line class="d-line" x1="191" y1="140" x2="196" y2="134"/>
<line class="d-line" x1="201" y1="140" x2="196" y2="134"/>
<line class="d-line d-dash" x1="268" y1="162" x2="268" y2="134"/>
<line class="d-line" x1="263" y1="140" x2="268" y2="134"/>
<line class="d-line" x1="273" y1="140" x2="268" y2="134"/>
<text class="d-small" x="160" y="152" text-anchor="middle">领取任务</text>
<rect class="d-box d-accent" x="20" y="162" width="64" height="40" rx="8"/>
<rect class="d-box d-dash" x="92" y="162" width="64" height="40" rx="8"/>
<rect class="d-box d-dash" x="164" y="162" width="64" height="40" rx="8"/>
<rect class="d-box d-dash" x="236" y="162" width="64" height="40" rx="8"/>
<text class="d-text" x="52" y="187" text-anchor="middle">worker</text>
<text class="d-text" x="124" y="187" text-anchor="middle">worker</text>
<text class="d-text" x="196" y="187" text-anchor="middle">worker</text>
<text class="d-text" x="268" y="187" text-anchor="middle">worker</text>
<line class="d-accent" x1="52" y1="202" x2="52" y2="244"/>
<line class="d-accent" x1="47" y1="238" x2="52" y2="244"/>
<line class="d-accent" x1="57" y1="238" x2="52" y2="244"/>
<text class="d-small d-mark" x="60" y="226">leader 设定副本数</text>
<text class="d-small" x="300" y="226" text-anchor="end">1 到 4 个副本</text>
<rect class="d-box" x="20" y="246" width="128" height="38" rx="8"/>
<text class="d-text" x="84" y="270" text-anchor="middle">Railway API</text>
<text class="d-small" x="160" y="262">设定副本数，</text>
<text class="d-small" x="160" y="277">无需重新部署</text>
</svg>
<figcaption>网站只负责把任务写进 PostgreSQL 的一张表。1 到 4 个 worker 副本领取任务，每个副本同一时间只处理一个；虚线框的副本只在有任务等待时运行。担任 leader 的 worker 还会通过 Railway API 设置副本数，Railway 据此启动或停止副本，无需重新部署。</figcaption>
</figure>

<h2 id="postgres-queue-or-broker">为什么把队列放在 Postgres 里，而不用消息代理？</h2>
<p>因为任务那一行本身就保存着消息代理（broker）只能引用的东西：归属、状态、积分预扣、价格和文件。用消息代理就多了一份需要保持同步的存储；而且大多数消息代理只保证至少投递一次，而乐队转谱是向外部服务发出的付费请求，绝不能发送两次。无论如何，我们都得自己制定任务归属规则。</p>
<p>负载也不大：排队的任务最多几十个，每个耗时几秒到几分钟。Railway 的队列指南说，如果你已经在用 Postgres，拿它做队列涉及的组件更少，只是吞吐量不如 Redis。pg-boss 这类队列库则会再多出一张任务表。等到每秒几十个任务时，我们再重新考虑。</p>

<h2 id="postgres-queue-multiple-workers">多个 worker 共用一个 Postgres 队列，怎样才不会领到同一个任务？</h2>
<p>每次认领都是一个短事务，整个队列共用一把锁。空闲的 worker 先不加锁检查有没有排队的任务，没有的话两秒后再看：</p>
<pre><code>-- One claim, slightly simplified
SELECT 1 FROM jobs WHERE status = 'queued' LIMIT 1;  -- nothing queued: stop here
SELECT pg_advisory_xact_lock(&lt;queue key&gt;);           -- held until COMMIT
SELECT * FROM jobs WHERE status = 'queued' ORDER BY created_at LIMIT 1;
UPDATE jobs SET status = 'running', worker_id = $1, heartbeat_at = $2
 WHERE id = $3;
COMMIT;</code></pre>
<p>第二个 worker 会在锁上等待，它的下一条查询要等第一个 worker 提交之后才开始。在 PostgreSQL 默认的 Read Committed 隔离级别下，查询能看到它开始前已经提交的数据，所以这个任务已经显示为运行中。在我们的测试中，6 个 worker 用各自独立的连接处理完了 40 个排队任务，每个任务都恰好只被领取一次。</p>
<p>worker 很多时，常规做法是 <code>SELECT … FOR UPDATE SKIP LOCKED</code>，PostgreSQL 文档也建议在类似队列的表上这样做。我们只用一把锁，是因为上传、积分变动和任务恢复本来就要用它（在本地的 SQLite 版本里对应的是 <code>BEGIN IMMEDIATE</code>），这样认领就和这些操作轮流进行。这部分等待时间我们还没有测量。队列也不需要独占数据库会话：不用 <code>LISTEN</code>（PgBouncer 的事务池模式不支持它），也不用我们以前那个唯一的 leader 所持有的会话级锁。</p>

<h2 id="worker-crash-recovery">worker 停止时，它手上的任务会怎样？</h2>
<figure class="flow-figure">
<ol class="flow-steps">
<li><b>排队中</b><span>网站保存上传的文件，并新增一行任务记录。</span></li>
<li><b>已认领</b><span>空闲的 worker 在锁的保护下领取最早的任务。</span></li>
<li><b>运行中</b><span>worker 每 15 秒汇报一次，附上当前所处的阶段。</span></li>
<li><b>已完成</b><span>乐谱保存完毕，worker 去找下一个任务。</span></li>
</ol>
<figcaption>一个任务的生命周期。每一步都是对该任务在 PostgreSQL 中那一行的修改，网站也是从这一行读取进度的。</figcaption>
</figure>
<p>任务会被恢复，付费请求也绝不会发送两次。被主动停止的 worker（比如部署时）会自己把手上的免费任务放回队列。崩溃的 worker 则只是从此没了动静。worker 每 15 秒把当前时间和所处阶段写进任务行；每个 worker 每 30 秒会在同一把锁下检查一遍，找出 120 秒租约已到期却没有汇报的运行中任务。免费转谱会被放回队列一次，再次中断就判为失败。乐队转谱会从保存下来的请求继续；没有保存请求的，就等待人工审核。如果某个 worker 只是慢了，发现自己的任务已经转给了别人，它会终止自己的进程，不写入任何结果。</p>
<p>只能执行一次的工作（比如控制器）在 leader 上运行：leader 是过去 45 秒内向 <code>workers</code> 表汇报过的 worker 中 ID 最小的那个，每个 worker 每 15 秒汇报一次。整个过程不持有任何锁，所以 leader 挂掉后，45 秒内就会有新的 leader 接替。</p>

<h2 id="queue-depth-autoscaling">为什么按队列深度而不是 CPU 来扩缩 worker？</h2>
<p>因为一个 worker 同一时间只运行一个任务，需要的 worker 数就等于排队中和运行中的任务数。忙碌 worker 的 CPU 只能说明它在忙，说明不了在等的是一个任务还是二十个；Railway 的自动扩缩容指南也把队列深度列为 worker 的扩缩信号。Railway 会自行把容器扩到 CPU 和内存上限，但副本数始终停在你设定的值；指南里的控制器扩容时一步到位，缩容时逐级递减。</p>
<p>我们的缩容方式不一样。API 只接收一个副本数，停掉哪个副本由 Railway 决定，所以我们只在没有排队和运行中的任务、也没有 worker 在忙时才缩容，而且直接缩到一个。</p>
<div class="table-scroll" role="region" aria-label="worker 控制器如何决策" tabindex="0">
<table>
<caption>控制器的决策规则（2026年10月7日的设定）</caption>
<thead><tr><th scope="col">规则</th><th scope="col" class="num">取值</th><th scope="col">原因</th></tr></thead>
<tbody>
<tr><th scope="row">检查队列的间隔</th><td class="num">30 秒</td><td>只有 leader 检查，所以每个决定只做一次。</td></tr>
<tr><th scope="row">保留的 worker 数与上限</th><td class="num">1 到 4 个</td><td>留一个随时接下一个任务；上限用来控制账单。</td></tr>
<tr><th scope="row">已有 worker 看似空闲时，任务等待多久才申请新 worker</th><td class="num">30 秒</td><td>空闲的 worker 通常 2 秒内就会领走任务；没有空闲 worker 时，下一次检查就会申请。</td></tr>
<tr><th scope="row">两次扩容之间的间隔</th><td class="num">90 秒</td><td>给新 worker 留出启动时间，再进行下一次申请。</td></tr>
<tr><th scope="row">等待新 worker 出现的时间</th><td class="num">300 秒</td><td>在此之前，控制器不会再次申请。</td></tr>
<tr><th scope="row">缩回一个之前的空闲时长</th><td class="num">600 秒</td><td>既没有任务、也没有忙碌的 worker 时，Railway 停掉的副本必然是空闲的。</td></tr>
</tbody>
</table>
</div>
<p>有任务排队时，控制器按每个排队中或运行中的任务一个 worker 来申请，至少比现有的多一个，最多四个。</p>

<h2 id="railway-api-failure">生产环境里出了什么问题？</h2>
<p>问题出在控制器第一次调用 Railway 的真实 API；我们的测试用的是一个对任何查询都照单全收的测试替身。启用控制器的改动合并五分钟后，每次检查队列都失败了。worker 照常领取任务，但日志和 Sentry 里只有 <code>RailwayError</code>：我们的日志不记录异常信息。</p>
<p>不带令牌发送同一个查询，就能复现。Railway 先按 schema 校验查询，返回了 HTTP 400 和 <code>GRAPHQL_VALIDATION_FAILED</code>。我们的服务按区域部署，所以写操作设置的是 <code>multiRegionConfig</code>，更新操作的输入类型接受这个字段；但读操作却向服务实例查询这个字段，而服务实例根本没有它。修复后改为从环境配置中读取副本数，我们的 Railway 配置文件和 <code>railway scale</code> 改的也是这份配置。</p>
<pre><code># Before: rejected with GRAPHQL_VALIDATION_FAILED
query ($service: String!, $environment: String!) {
  serviceInstance(serviceId: $service, environmentId: $environment) {
    numReplicas multiRegionConfig
  }
}

# After: the count is at services.&lt;service id&gt;.deploy.multiRegionConfig
query ($environment: String!) {
  environment(id: $environment) { config(decryptVariables: false) }
}

# The write, unchanged; input: {"multiRegionConfig": {"&lt;region&gt;": {"numReplicas": 2}}}
mutation ($service: String!, $environment: String!, $input: ServiceInstanceUpdateInput!) {
  serviceInstanceUpdate(serviceId: $service, environmentId: $environment, input: $input)
}</code></pre>
<p>现在日志会记录 Railway 自己返回的错误信息（其中从不包含令牌），因为令牌被拒和查询被拒返回的都是 HTTP 400。测试替身现在也会拒绝旧查询。我们还加了一项新检查：用占位 ID、不带令牌，把两个操作发到线上 schema。GraphQL 服务器会先校验再执行，所以字段写错会在校验阶段失败，而合法的查询会一路走到 Railway 返回“Not Authorized”。</p>
<figure class="timeline-figure">
<ol class="timeline">
<li><time datetime="2026-10-06">10月6日</time><p>队列可容纳的等待任务数提高到原来的数倍，但仍然只有一个 worker 在处理。</p></li>
<li><time datetime="2026-10-07T04:22Z">10月7日 04:22</time><p>合并：worker 共享队列，网站旁边新增一个 worker 服务。测试中，两个上传同时运行。</p></li>
<li><time datetime="2026-10-07T04:48Z">04:48</time><p>合并：网站不再接任务，控制器启用。</p></li>
<li><time datetime="2026-10-07T04:53Z">04:53</time><p>每次检查队列都以 <code>RailwayError</code> 失败。任务照常运行。</p></li>
<li><time datetime="2026-10-07T05:07Z">05:07</time><p>合并：改为从环境配置中读取副本数。</p></li>
<li><time datetime="2026-10-07T05:12Z">05:12</time><p>第一次从 Railway 读到副本数：一个 worker，状态平稳。</p></li>
<li><time datetime="2026-10-07T05:17Z">05:17</time><p>同时来了两个任务。控制器申请第二个 worker，17 秒后它就启动了。</p></li>
<li><time datetime="2026-10-07T05:28Z">05:28</time><p>空闲 602 秒后缩回一个。直到 06:28 都没有出错。</p></li>
</ol>
<figcaption>上线过程，摘自我们的验证记录。时间均为 UTC。</figcaption>
</figure>
<p>同一天，leader 开始保存每次检查的结果，供网站的健康状态页使用：Railway 拒绝令牌，或者两分钟内没有任何检查，状态页都会发出警告。网站自身的健康检查已不再覆盖 worker，所以 worker 故障现在会体现在每五分钟一次的服务检查里；某个服务连续两次不健康，Sentry 就会发邮件通知我们。</p>

<h2 id="not-verified-yet">还没验证的部分，以及成本</h2>
<ul>
<li>worker 上跑过的只有免费转谱；乐队转谱在 worker 上的运行，以及 worker 丢失后的恢复，都还没测过。</li>
<li>我们的测试账号最多只能同时处理两份乐谱，而且我们跳过了原计划几天的 dry-run 日志观察，所以控制器只经历过一次小规模突发和一小时的空闲。三到四个 worker、90 秒的扩容间隔、停掉忙碌的副本，这些都还没在生产环境中发生过。</li>
<li>生产环境没有记录任务被领取的时间，所以我们还说不出排队等待缩短了多少。在扩容测试中，新 worker 启动后无事可做：第一个 worker 已经领走了那个等待中的任务。</li>
<li>空闲十分钟后 worker 池会缩回一个，所以突发的任务最多要等 30 秒才轮到下一次检查，然后还要等新 worker 启动：在我们唯一的一次测试中是 17 秒。</li>
<li>达到上限时，同时运行四个任务，其余的排队等待。</li>
<li>每次部署都会重启所有 worker，正在运行的免费转谱会被放回队列一次；CI 每次应用我们的 Railway 配置时，也会把副本数重置为一，直到下一次检查。</li>
</ul>
<p>Railway 按分钟计费，每 vCPU 每月 US$20，每 GB 内存每月 US$10（2026年10月7日查看）。我们估算，一个 worker 以 2 vCPU、1.5 GB 转谱时每小时约 US$0.08，等待时则少得多。Railway 上的硬性用量上限一旦触及，所有工作负载都会下线，所以调高 worker 上限时，要同时调高这类用量上限。</p>

<h2 id="worker-autoscaling-checklist">如果你也想自己搭</h2>
<ul>
<li>除非需要消息代理的路由能力或吞吐量，否则就把队列和任务状态放在一起。</li>
<li>在一个短事务里完成认领；worker 多时用 <code>SKIP LOCKED</code>。</li>
<li>队列不要依赖会话状态：不用 <code>LISTEN</code>，不用会话级锁。</li>
<li>给每个运行中的任务指定归属和心跳；任务被收回的 worker 要直接停止，不写入任何结果。付费的外部请求绝不重发，而是凭保存的 ID 继续。</li>
<li>按排队中加运行中的任务数扩缩容；如果停掉哪个副本由平台决定，就只在没有 worker 忙碌时缩容。</li>
<li>用平台线上的 schema 测试它的 API 调用；确认错误信息里不含机密后，再把它们记进日志。</li>
<li>给控制器一个汇报状态的地方。</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS">13.3. 显式锁定：13.3.5 咨询锁，PostgreSQL 18 文档</a> — PostgreSQL 全球开发组</li>
<li><a href="https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS">9.28. 系统管理函数：9.28.10 咨询锁函数，PostgreSQL 18 文档</a> — PostgreSQL 全球开发组</li>
<li><a href="https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE">SELECT：锁定子句，PostgreSQL 18 文档</a> — PostgreSQL 全球开发组</li>
<li><a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED">13.2. 事务隔离：13.2.1 读已提交隔离级别，PostgreSQL 18 文档</a> — PostgreSQL 全球开发组</li>
<li><a href="https://www.pgbouncer.org/features.html">PgBouncer 功能特性</a> — PgBouncer</li>
<li><a href="https://docs.railway.com/guides/autoscale-horizontally">按负载自动水平扩展服务</a> — Railway</li>
<li><a href="https://docs.railway.com/guides/cron-workers-queues">定时任务、后台 worker 与队列，该选哪个</a> — Railway</li>
<li><a href="https://docs.railway.com/pricing/plans">价格方案</a> — Railway</li>
<li><a href="https://docs.railway.com/pricing/cost-control">成本控制</a> — Railway</li>
<li><a href="https://spec.graphql.org/October2021/#sec-Validating-Requests">GraphQL 规范（2021年10月版）：6.1.1 校验请求</a> — GraphQL 基金会</li>
</ol>
</section><h2>常见问题</h2><h3>Railway 会自动水平扩展副本吗？</h3><p>截至 2026年10月7日不会。Railway 会在上限内给容器分配更多 CPU 和内存，但服务的副本数始终停在你设定的值。它的自动扩缩容指南让你自己运行一个控制器，读取负载信号，再调用 API 的 <code>serviceInstanceUpdate</code>。</p><h3>Postgres 任务队列该用 SKIP LOCKED 还是咨询锁？</h3><p><code>FOR UPDATE SKIP LOCKED</code> 能让多个 worker 同时认领不同的行，PostgreSQL 文档也建议在类似队列的表上使用它。用一把事务级咨询锁（advisory lock），则会让认领依次进行；worker 不多时这样更简单，尤其是这把锁本来就在保护你的其他写操作时。</p><h3>为什么 Railway API 对 multiRegionConfig 返回 GRAPHQL_VALIDATION_FAILED？</h3><p>我们遇到的原因是：查询让服务实例返回 <code>multiRegionConfig</code>，而这个字段只有更新操作的输入类型里才有。应该从 <code>environment(id) { config }</code> 的 <code>services.&lt;service id&gt;.deploy.multiRegionConfig</code> 读取副本数，再用 <code>serviceInstanceUpdate</code> 写入。</p><h3>移除 worker 副本时，正在运行的任务会怎样？</h3><p>我们的控制器只在没有 worker 忙碌时才移除副本。如果忙碌的 worker 仍被停掉（比如部署时），它会把免费转谱任务放回队列一次；如果它直接消失，另一个 worker 会在 120 秒收不到汇报后把任务收回。付费请求绝不会重复发送。</p><h3>在 Railway 上多一个 worker 要花多少钱？</h3><p>Railway 按分钟计费，每 vCPU 每月 US$20，每 GB 内存每月 US$10（2026年10月7日查看）。我们估算，一个 worker 以 2 vCPU、1.5 GB 转谱时每小时约 US$0.08，等待时则少得多。我们最多只开四个 worker。</p>]]></content:encoded>
    </item>
    <item>
      <title>ChatGPT 和 Claude 能把录音转成乐谱吗？</title>
      <link>https://scorestarling.com/zh-hans/blog/chatgpt-claude-sheet-music</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/chatgpt-claude-sheet-music</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>技术</category>
      <description>单靠它们自己不行；通过 MCP 接入扒谱工具后，Claude 和 ChatGPT 就能把录音变成可编辑的乐谱。本文介绍设置方法、一次完整的示例对话和目前的限制。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>单靠它们自己不行。AI 聊天助手可以聊音乐，也能用文本写出音符，但没法根据你自己的录音给出一份可靠、可编辑的乐谱；我们在 2026年10月3日查阅了两家公司的文档，都没有提到可以把音频转成乐谱。通过 MCP 接入扒谱工具后，两者就都能做到：工具负责识别音符、排版乐谱，助手负责把你的要求变成工具调用。ScoreStarling 就是这样和 Claude、ChatGPT 配合的：它正在等待两边应用目录的审核，不过你现在就能通过地址添加。</p>
</section>

<h2 id="what-happens-if-you-ask">直接让 ChatGPT 或 Claude 扒谱会怎样？</h2>
<p>你得到的是对音乐的文字描述，或者助手自己写出的音符，而不是对你这个文件的转谱。Claude 帮助中心列出的可上传文件是文档（PDF、DOCX、CSV、TXT 等）和图片（JPEG、PNG、GIF、WebP），里面没有任何音频格式。OpenAI 的音频文件转录指南讲的是把语音录音转成文字稿，没有提到音乐或记谱。以上两项均于 2026年10月3日查阅。</p>
<p>助手能做的，是用文本写音乐。比如 ABC 记谱法就是纯文本，软件可以把它画成五线谱；MCP Apps 的官方示例 Sheet Music Server 会把助手写出的 ABC 渲染成可以播放的乐谱。记录灵感时这很有用，但它不是转谱：没有任何东西测量过你录音里的音高和节奏，所以每个音你都得靠耳朵核对。</p>
<p>一份能用的转谱，背后是一连串工作：从音频里找出每个音的音高、起点和时值；把音符对齐到拍子和拍号上；确定调号、选定变音记号的写法、放好休止符、分开声部；最后排版成谱，存成打谱软件能打开的格式。每一步都有专门的软件。第一步由 Spotify 开源的 Basic Pitch 完成，我们在自己的服务器上用它处理单件乐器或人声，它的作者说它一次只处理一件乐器时效果最好。中间几步由 MuseScore Studio 4.7.5 的 MIDI 导入功能替我们完成，最后由 Verovio 用 Leipzig 音乐字体排版。</p>

<h2 id="what-mcp-changes">接入 MCP 工具后有什么不同？</h2>
<p>助手可以把音乐交给专门的软件处理，而你照样用日常的话提要求。用官方文档的话来说，模型上下文协议（Model Context Protocol）是“一个把 AI 应用连接到外部系统的开源标准”。服务方运行一个提供工具的 MCP 服务器，连上它的助手就能在对话中途调用这些工具。在 Claude 里，你自己添加的服务器叫作自定义连接器；在 ChatGPT 里，它是你在开发者模式下创建的应用。</p>
<p>连接 ScoreStarling 时，浏览器会打开我们的登录页面。你授权之后，助手只能处理你自己的项目，碰不到别人的。之后的分工如下：</p>
<div class="table-scroll" role="region" aria-label="AI 助手和 ScoreStarling 的工具各做什么" tabindex="0">
<table>
<caption>连接后的对话中，谁负责什么</caption>
<thead>
<tr><th scope="col">环节</th><th scope="col">AI 助手</th><th scope="col">ScoreStarling 的工具</th></tr>
</thead>
<tbody>
<tr><th scope="row">你的要求</th><td>理解“第 1 小节最后一个音，高八度”指的是什么，并选择工具</td><td>返回乐谱的音符、小节和版本，让要求能对应到具体的音符</td></tr>
<tr><th scope="row">录音</th><td>转交你的附件（ChatGPT），或从代码沙箱上传（Claude）</td><td>签发一次性上传链接，10 分钟后失效</td></tr>
<tr><th scope="row">转谱</th><td>查询任务状态，而不是重新上传</td><td>识别音符（单件乐器或人声用 Basic Pitch），写成乐谱并用 Verovio 排版</td></tr>
<tr><th scope="row">修改</th><td>把你的话变成一次精确的编辑，自己的建议则以预览形式给出</td><td>执行编辑，支持撤销和版本保存；生成带音频的预览</td></tr>
<tr><th scope="row">导出</th><td>在对话里把文件交给你，或者引导你去面板的 Download 菜单</td><td>生成 PDF、MusicXML、MIDI、MP3 或 WAV；链接一小时后失效</td></tr>
</tbody>
</table>
</div>
<p>这些工具和<a href="https://scorestarling.com/app">网页工作台</a>操作的是同一份已保存的乐谱，所以在对话里做的修改，打开工作台就能看到。</p>

<h2 id="a-session-step-by-step">从录音到 PDF，一次完整的对话是什么样的？</h2>
<p>几句简单的话，就能从录音走到最终文件：转谱、修改、预览、导出，中间随时试听。下面的步骤用的是我们<a href="https://scorestarling.com/zh-hans/mcp#ask">连接指南</a>里的示例提示词。这些步骤大多已在 ChatGPT 的真实对话中跑通，只是没有在同一段对话里一次走完：2026年10月5日转了一段附件录音；10月6日测试了从链接转谱、乐谱面板、播放、预览、应用、撤销和下载。在 Claude 中，10月6日一份直接用音符写成的乐谱在面板里打开并成功播放；从 Claude 上传录音仍在测试中。</p>
<ol>
<li>附上录音，然后说：“用 ScoreStarling 把这段语音备忘录转成乐谱，速度大约 84 BPM。”如果知道速度，就告诉它。没给速度时，ScoreStarling 会根据录音估算；如果演奏带摇摆或弹性速度，估出来的可能是一个相关但不同的速度。</li>
<li>文件在两款应用里的传递方式不同。ChatGPT 会把附件直接交给转谱工具。Claude 会在代码沙箱里读取文件，向 ScoreStarling 申请一次性上传链接，再把文件传过去，所以沙箱需要联网权限。如果你用的应用里没法上传，助手可以打开 ScoreStarling 的上传面板，你也可以在网页工作台上传这段录音。</li>
<li>我们的服务器会按你给的速度或检测到的速度转谱。人声或单件乐器先按 4/4 拍记谱，之后可以修改；钢琴独奏还会自动判断拍号和小节线。如果你的应用能显示面板，助手会在对话里的面板中打开乐谱。同一个项目也在工作台里：工作台能把转谱结果和你的录音同步播放，支持交叉淡化和循环播放，方便你听出两者哪里对不上。</li>
<li>听到问题就改：“我的 ScoreStarling 乐谱里，第 1 小节最后一个音应该高八度。”助手会读取乐谱找到这个音，然后做出精确的修改。你明确说出的修改会立即生效，也可以撤销。</li>
<li>改动较大时，先试再定：“把我的 ScoreStarling 乐谱升高一个全音，先让我听听预览。”工具会生成移调后的乐谱和音频作为预览。在你接受之前，乐谱不会变化；你也可以直接放弃这个预览。</li>
<li>导出：“把我的 ScoreStarling 乐谱导出为 PDF 和 MusicXML。”你可以在对话里或面板的 Download 菜单拿到这两个文件，链接一小时后失效。PDF 可以直接打印，乐谱解锁前页脚会有一行小字；MusicXML 可以在 MuseScore、Dorico、Sibelius 或 Finale 里打开继续编辑。导出 MusicXML 需要先解锁乐谱，一次性花费 15 积分，助手会在花积分之前先征求你的同意。</li>
</ol>
<p>指南里还有两句值得一记。“用长笛播放我的 ScoreStarling 旋律”会更换播放和导出音频时用的 General MIDI 音色。“我的 ScoreStarling 账号里还有任务在运行吗？”能让你在开始新任务前查看任务进度，因为免费账号同时只能运行一个任务。</p>

<h2 id="connect-claude">如何在 Claude 中连接 ScoreStarling？</h2>
<p>把 ScoreStarling 添加为自定义连接器（custom connector）。Anthropic 帮助中心表示，免费版、Pro、Max、Team 和 Enterprise 套餐都能使用自定义连接器，免费版账号限一个（2026年10月3日查阅）。</p>
<ol>
<li>打开 Customize → Connectors，选择 + Add，再选择 Add custom connector。名称填 ScoreStarling，地址填 <code>https://mcp.scorestarling.com/mcp</code>。</li>
<li>在 Authentication 下选择 Sign in now，在 OAuth client 下选择 Register automatically。Claude 推荐的是使用它公开的身份（published identity），但这个选项依赖客户端 ID 元数据文档（client ID metadata documents），而我们的登录服务器暂时还不支持。</li>
<li>登录 ScoreStarling。在对话中点击输入框里的 +，打开 Connectors，然后开启 ScoreStarling。</li>
</ol>
<p>在 Team 和 Enterprise 套餐中，需要由所有者先在 Organization settings → Connectors 里添加连接器，成员再选择 Connect。要转谱你附上的录音，必须允许 Claude 的代码沙箱访问 ScoreStarling，<a href="https://scorestarling.com/zh-hans/mcp#claude-upload">指南里有这项设置的说明</a>。从 Claude 上传仍在测试中，所以上传面板和工作台仍是备用方案。<a href="https://scorestarling.com/zh-hans/mcp#claude">Claude 设置步骤</a>里还有一个按钮，可以替你自动填好表单；Anthropic 没有为这个快捷方式提供文档，所以旁边也列出了手动步骤。</p>
<p>在 Claude Code 里只需一条命令，然后在会话中输入 <code>/mcp</code> 登录：</p>
<pre><code>claude mcp add --transport http --scope user scorestarling https://mcp.scorestarling.com/mcp</code></pre>

<h2 id="connect-chatgpt">如何在 ChatGPT 中连接 ScoreStarling？</h2>
<p>使用开发者模式。OpenAI 向网页版的 Plus、Pro、Business、Enterprise 和 Education 账号提供这项功能（2026年10月3日查阅）。下面的菜单名称取自 ChatGPT 英文界面，中文界面中的叫法可能不同。</p>
<ol>
<li>在 Settings → Security and login 中打开 Developer mode。在 Business、Enterprise 和 Edu 工作空间中，可能需要管理员先允许。</li>
<li>打开 Plugins，选择 +，把应用命名为 ScoreStarling，并粘贴 <code>https://mcp.scorestarling.com/mcp</code>。认证方式选择 OAuth，使用动态客户端注册（dynamic client registration）；不需要 API 密钥。</li>
<li>登录 ScoreStarling。应用会出现在 Drafts 下；在输入框的 Developer mode 工具里选中它即可。</li>
</ol>
<p>OpenAI 的指南把开发者模式标为高风险功能，并说明写入操作默认需要确认，所以工具修改内容之前，ChatGPT 会先征求你的同意。ScoreStarling 正在等待 ChatGPT 应用目录的审核，所以目前在目录里还搜不到。如果找不到 Developer mode 或 + 按钮，<a href="https://scorestarling.com/zh-hans/mcp#chatgpt">ChatGPT 设置步骤</a>里说明了该检查什么。</p>

<h2 id="what-doesnt-work-yet">目前还有哪些做不到？</h2>
<p>有些功能还在测试，有些不在服务范围内。产品限制于 2026年10月7日核实，客户端菜单于 10月3日查阅。</p>
<ul>
<li>ScoreStarling 可以免费上手。单件乐器或人声、钢琴独奏、导入、编辑，以及下载 PDF、MP3 和 WAV 都免费；2026年内每个账号每月可免费获得 100 积分，2027年1月起改为 50 积分。乐队转谱要用积分，解锁乐谱的 MusicXML 和 MIDI 需要 15 积分（详见<a href="https://scorestarling.com/zh-hans/pricing">价格</a>）。在对话中，ScoreStarling 只使用你已有的积分，不会向你出售任何东西。免费账号同时只能处理一份乐谱，Plus 账号可以同时处理五份。</li>
<li>ChatGPT 和 Claude 的目录里都还没有它：我们在 2026年10月6日向两边提交了申请，目前正在等待审核。上架之前，在 ChatGPT 中需要使用开发者模式。</li>
<li>在我们的测试中，对话里的乐谱面板在 ChatGPT 和 Claude 中都能打开，ChatGPT 也已经把附件录音成功传给了 ScoreStarling；从 Claude 和其他客户端上传仍在测试中。每一步都可以改用网页工作台完成。</li>
<li>上传文件最大 100 MiB，无论选哪种，都只处理录音的前 5 分钟。你可以上传音频或视频、粘贴链接，或者直接在浏览器里录音。请按录音里的内容选择对应选项，详见<a href="https://scorestarling.com/zh-hans/how-it-works#engines">选项说明</a>。</li>
<li>不指定速度时会自动检测。人声或单件乐器先按 4/4 拍记谱，之后可以修改；钢琴独奏会自动判断 4/4、3/4、6/8 或 12/8 拍，以及小节线和弱起小节；乐队乐谱的拍号由演奏判断，或在开始前手动选择。所有估算结果都需要检查。TAB 谱不记推弦、滑音和击弦；导入的乐谱和简谱视图另有各自的<a href="https://scorestarling.com/zh-hans/how-it-works#specs">限制</a>。</li>
<li>远程 MCP 现在也能识别乐谱 PDF 和图片、读取乐谱文件，并能根据 ABC 或 MusicXML 文本创建乐谱。这类记谱文本助手可以自己写；作曲由助手完成。ScoreStarling 的插件（含两个技能）也在等待目录审核。<a href="https://scorestarling.com/zh-hans/mcp#webmcp">WebMCP Beta</a> 则是另一条途径，供在浏览器里打开工作台的 AI 智能体使用。</li>
<li>它不会生成新的录音。导出的 MP3 和 WAV 是用 General MIDI 音色把乐谱合成出来的音频，不是从你的录音里分离出的分轨。</li>
<li>每份转谱结果都只是初稿。我们 10月2日的对比测试测的是吉他录音和一段乐队片段，没有测人声或钢琴独奏，结果已发表在《<a href="https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy">AI 扒谱到底有多准？我们实测了一下</a>》一文中。分享乐谱之前，请先听一遍并修正。</li>
</ul>

<h2 id="other-ways">除了 ScoreStarling，还有哪些办法？</h2>
<p>也有其他服务把音乐工具接入 AI 助手。以下内容依据各家自己的页面，于 2026年10月3日查阅。</p>
<ul>
<li>Mirelo 在 <code>https://mcp.mirelo.ai/mcp</code> 提供托管的 MCP 服务器。用 Mirelo 账号登录即可，不需要 API 密钥；它的指南涵盖 Claude、开发者模式下的 ChatGPT、Cursor、Claude Code 和 Codex。它在 2026年7月30日的发布公告中说，Audio-to-MIDI 可用于现有录音；其插件仓库中的 Audio-to-MIDI 技能会返回 MIDI 和 MusicXML 下载，不指定速度时会自动检测。它的其他工具大多用于生成和编辑音效。Mirelo 同时也是 ScoreStarling 里乐队转谱的合作伙伴，所以我们对它并不中立。</li>
<li>Melogen 的 MCP 服务器面向开发者模式下的 ChatGPT，通过 OAuth 登录；它能把 PDF、PNG 或 JPG 格式的印刷乐谱识别成 MIDI 和 MusicXML，还能分析歌曲的速度、调性和结构。它公开的插件里没有把录音转成乐谱的工具。</li>
<li>如果你更想全部在自己的电脑上运行，可以试试 audio2score-mcp：这是一个开源的本地服务器，把 Basic Pitch 和 music21 串联起来，输出 <code>.mid</code> 和 <code>.musicxml</code> 文件，并附有 Claude Code 的配置说明。它不需要账号，最近一次提交是在 2026年8月20日。</li>
<li>不用 AI 助手的话，直接在 ScoreStarling 的网页工作台上传录音，得到的是同样的乐谱。</li>
</ul>
<p>我们在《<a href="https://scorestarling.com/zh-hans/blog/sheet-music-mcp-servers">乐谱 MCP 服务器盘点：各自能做什么</a>》一文中并排比较了其中 26 个，包括操控 MuseScore 的服务器和用 music21 分析乐谱的服务器。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://modelcontextprotocol.io/docs/getting-started/intro">What is the Model Context Protocol (MCP)?</a>——Model Context Protocol 文档</li>
<li><a href="https://support.claude.com/en/articles/8241126-upload-files-to-claude">Upload files to Claude</a>——Claude 帮助中心</li>
<li><a href="https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp">Get started with custom connectors using remote MCP</a>——Claude 帮助中心</li>
<li><a href="https://support.claude.com/en/articles/12111783-create-and-edit-files-with-claude">Create and edit files with Claude</a>——Claude 帮助中心</li>
<li><a href="https://developers.openai.com/api/docs/guides/developer-mode">ChatGPT Developer mode</a>——OpenAI</li>
<li><a href="https://developers.openai.com/api/docs/guides/speech-to-text">File transcription</a>——OpenAI API 文档</li>
<li><a href="https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server">Example: Sheet Music Server</a>——MCP Apps（modelcontextprotocol/ext-apps），GitHub</li>
<li><a href="https://github.com/spotify/basic-pitch">Basic Pitch</a>——Spotify，GitHub</li>
<li><a href="https://mirelo.ai/mcp">Mirelo MCP: sound design for any AI agent</a>——Mirelo</li>
<li><a href="https://mirelo.ai/blog/introducing-mirelo-mcp">Generate Sound Effects in Claude, ChatGPT and Cursor</a>——Mirelo 博客，2026年7月30日</li>
<li><a href="https://github.com/mirelo-ai/mirelo-plugins">Mirelo plugins for Cursor and Claude</a>——Mirelo，GitHub</li>
<li><a href="https://melogenai.com/mcp">Melogen Music Intelligence for ChatGPT and MCP</a>——Melogen AI</li>
<li><a href="https://github.com/David7ce/audio2score-mcp">audio2score-mcp</a>——David7ce，GitHub</li>
</ol>
</section><h2>常见问题</h2><h3>在 ChatGPT 或 Claude 里用乐谱连接器，需要付费套餐吗？</h3><p>ChatGPT 需要：开发者模式只向网页版的 Plus、Pro、Business、Enterprise 和 Education 账号开放。Claude 的所有套餐都能添加自定义连接器，免费版限一个。以上两项均于 2026年10月3日查阅。在 ScoreStarling 上，单件乐器或人声、钢琴独奏和编辑都免费；乐队转谱要用积分，解锁乐谱的 MusicXML 和 MIDI 也要用积分。2026年内，每个账号每月可免费获得 100 积分，2027年1月起改为每月 50 积分（2026年10月7日核实）。</p><h3>ScoreStarling 已经上架 ChatGPT 或 Claude 的应用目录了吗？</h3><p>还没有。我们在 2026年10月6日向两边的目录提交了申请，目前正在等待审核。在此之前，请按<a href="https://scorestarling.com/zh-hans/mcp">连接指南</a>的说明，通过地址 <code>https://mcp.scorestarling.com/mcp</code> 添加 ScoreStarling。任何人都可以用 Google 账号或邮箱登录。</p><h3>能扒整支乐队的录音吗？</h3><p>可以：选择乐队即可。乐队转谱按每分钟 40 积分计费，开始前会先显示价格。乐队录音由我们的合作伙伴 Mirelo 转谱，和所有转谱结果一样，需要你检查一遍。单件乐器或人声、钢琴独奏都免费。无论选哪种，都只处理录音的前 5 分钟，上传文件最大 100 MiB。</p><h3>AI 助手会不经我同意就改动乐谱吗？</h3><p>你明确说出的修改，比如“把第 1 小节最后一个音升高八度”，会立即生效，也可以撤销。助手自己提出的修改，以及你要求先试听的修改，都会以预览的形式出现；在你接受之前，乐谱不会有任何变化。</p><h3>AI 助手能根据我的录音写出吉他谱吗？</h3><p>能，2026年10月8日起支持。向助手要六线谱（TAB），ScoreStarling 就会按你指定的定弦和变调夹位置，为吉他、贝斯或尤克里里声部显示六线谱：可以放在五线谱下方，也可以只显示六线谱。助手还能把某个音挪到另一根弦上，或者把六线谱以文本形式贴进回答里。推弦、滑音和击弦目前不会标出。另有一个在本地运行的开源 MCP 服务器 fingerstyle-tab-mcp，也能把吉他录音转成文本六线谱；我们在《<a href="https://scorestarling.com/zh-hans/blog/sheet-music-mcp-servers">乐谱 MCP 服务器盘点：各自能做什么</a>》一文中把它和其他服务器列在了一起。</p>]]></content:encoded>
    </item>
    <item>
      <title>AI 扒谱到底有多准？我们实测了一下</title>
      <link>https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>研究</category>
      <description>AI 扒谱出初稿够用，出成品还不行。我们用 12 段公开吉他录音和一段 60 秒乐队片段实测了 Basic Pitch 与 Mirelo 两款引擎，用大白话讲清起音 F1，并列出结果和局限。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>出初稿够用，出成品还不行。2026年10月2日，我们用 12 段公开吉他录音做了测试：Spotify 开源的 Basic Pitch 起音 F1 达到 0.744，Mirelo 的 Audio-to-MIDI 为 0.471；1.0 意味着每个音符的音高和时间都对，而且没有一个多余的音。六段独奏录音里有三段 Mirelo 一个音都没返回，但在一段 60 秒的乐队片段上，它以 0.672 比 0.520 领先。音符时长远不如音符起点可靠；而且这些数字都说明不了写出来的乐谱好不好读。</p>
</section>

<h2 id="what-onset-f1-means">起音 F1（onset F1）衡量的是什么？</h2>
<p>起音 F1 把扒谱结果和参考标注逐个音符比对，漏掉的音和多出来的音同样扣分。扒出的一个音符，只要音高与某个参考音符相差不超过 50 音分（四分之一音），起点相差不超过 50 毫秒，就算匹配。每个参考音符只能被匹配一次。</p>
<p>匹配完会得到两个比例。精确率是模型写出的音符中匹配上的比例；召回率是参考音符中被模型找到的比例。F1 是两者的调和平均数，只要其中一个低，F1 就高不起来。假设一段音乐有 100 个音：一个模型写了 100 个音，对了 75 个，得分 0.75；另一个模型找到了其中 90 个，却为此写了 150 个音，召回率 0.9，精确率 0.6，F1 只有 0.72——多写的 60 个音拉低了分数。</p>
<p>起止 F1（onset+offset F1）再加一个条件：每个音符的结束点也要落在参考音符时长的 20% 以内，如果 50 毫秒更长，就按 50 毫秒算。Spotify 的 Basic Pitch 论文以起音 F1 为主要指标，因为音符结束点不那么客观，论文点出了混响、延音踏板和标注方式这几个原因。两个指标我们都会列出。但两者都不检查音符被分给了哪件乐器，也不管调号和节奏该怎么记。</p>

<h2 id="how-we-tested">我们的测试方法</h2>
<p>我们用的是 GuitarSet 1.1.0（Xi 等，ISMIR 2018）。这个公开数据集收录了 360 段约 30 秒的吉他片段：六位吉他手弹奏同样的 30 份旋律谱，涵盖五种风格，每份先弹一遍伴奏（comp），再在伴奏之上弹一遍独奏。录音用的是六路分弦拾音器（hexaphonic pickup），每根弦各有一路独立信号，作者因此能把音符标注基本自动化。</p>
<p>我们选了 12 段完整录音，每位吉他手一段独奏、一段伴奏，用的是单声道麦克风音轨，共 433 秒。选哪几段在运行任何模型之前就定好了：先排除三段已知标注有误的录音（<a href="https://github.com/marl/GuitarSet/issues">GuitarSet 的 issue #4 和 #5</a>：一个重复的音符和错误的时间），再对每位吉他手的每种演奏方式，取“scorestarling-v1:”加文件名的 SHA-256 哈希值排序最靠前的文件。下文的<a href="#per-recording">逐段结果表</a>列出了选中的 12 段。</p>
<p>打分用的是 <a href="https://mir-eval.readthedocs.io/latest/api/transcription.html">mir_eval</a> 0.8.2，容差采用上文所述的默认值，先逐段计算再取平均，让每段录音的权重相同。没有做任何调参：不加时间偏移，不做过滤，也不做对齐平移。Basic Pitch 0.4.0 在一台 Mac 上运行，两次完整运行的输出逐字节一致。Mirelo 的 Audio-to-MIDI API（返回的模型版本为 a2m-1.1）在 2026年10月2日处理了同样的 12 段录音，没有附带可选的乐器列表；据 <a href="https://mirelo.ai/changelog">Mirelo 的更新日志</a>，其 API 自 2026年8月6日起支持这个参数。</p>
<p>以下所有结果都受四点限制：</p>
<ul>
<li>Basic Pitch 的论文把 GuitarSet 和 Slakh 列入了训练数据，所以两项测试都对它有利。我们无法确认这 12 段 GuitarSet 录音是否在它的训练集里；乐队片段则取自 Slakh 的测试集。</li>
<li>12 段吉他录音加一段 60 秒乐队片段，只是诊断性的小样本，不是基准测试。人声和真实录制的乐队演奏都没有测；钢琴只在合成音频上测过，用的也是另一个模型（见<a href="#faq">文末问答</a>）。</li>
<li>吉他录音在 Mirelo 上只跑了一次，是在 10月2日。它没返回任何音符的那三段，我们也没有换个方式重试，比如告诉它录音里是原声吉他。乐队片段在 10月5日重新提交了一次，返回的音符完全相同。</li>
<li>这些数字都不评价写出来的乐谱。像 MV2H 这样在乐谱层面打分、还会检查拍号、声部和时值的指标，我们还没有测。</li>
</ul>
<p class="note">ScoreStarling 扒单件乐器或人声用的是 Basic Pitch，扒乐队用的是我们的合作伙伴 Mirelo，所以我们并不是中立的旁观者。正因如此，我们在这里把方法、样本和局限都写清楚。</p>

<h2 id="basic-pitch-vs-mirelo">Basic Pitch 和 Mirelo，谁扒吉他更准？</h2>
<p>Basic Pitch 胜出：独奏旋律上遥遥领先，伴奏上小幅领先。</p>
<div class="table-scroll" role="region" aria-label="Basic Pitch 与 Mirelo 在 12 段 GuitarSet 录音上的起音 F1" tabindex="0">
<table>
<caption>GuitarSet，12 段录音，宏平均，2026年10月2日</caption>
<thead>
<tr><th scope="col">录音</th><th scope="col" class="num">Basic Pitch 起音 F1</th><th scope="col" class="num">Mirelo 起音 F1</th><th scope="col" class="num">Basic Pitch 起止 F1</th><th scope="col" class="num">Mirelo 起止 F1</th></tr>
</thead>
<tbody>
<tr><th scope="row">独奏（6 段）</th><td class="num">0.818</td><td class="num">0.301</td><td class="num">0.654</td><td class="num">0.168</td></tr>
<tr><th scope="row">伴奏（6 段）</th><td class="num">0.669</td><td class="num">0.642</td><td class="num">0.362</td><td class="num">0.280</td></tr>
<tr><th scope="row">全部（12 段）</th><td class="num">0.744</td><td class="num">0.471</td><td class="num">0.508</td><td class="num">0.224</td></tr>
</tbody>
</table>
</div>
<p class="note">2026年10月4日更新：ScoreStarling 的单件乐器选项做了两处改动——整体持续偏高或偏低的录音会先校准到 A440，旋律改为一次只写一个音——之后我们用同样的 12 段录音重测了一遍。独奏得分 0.846，伴奏 0.679，总体 0.763，算上音符结束点则为 0.522。上表保留的仍是 10月2日单独运行 Basic Pitch 的结果。</p>
<p>六段独奏录音中，有三段 Mirelo 一个音符都没返回。这三段在它的独奏平均分里按 0 计算，因为用户拿到的就是空结果；我们也没有重跑。返回了音符的那几段，表现有好有坏。第一段 00_BN3-154-E_solo 上，两个引擎在音符起点上打成平手（Basic Pitch 0.809，Mirelo 0.814），但 Mirelo 的音符结束点偏差更大（起止 F1 为 0.320，Basic Pitch 为 0.524），还把这首 154 BPM 的曲子判成了约 76.9 BPM，只有原速的一半。伴奏方面，六段里有两段 Mirelo 明显更好。12 段全部算上，它的起止 F1 为 0.224。</p>
<div class="table-scroll" id="per-recording" role="region" aria-label="Basic Pitch 与 Mirelo 逐段录音的起音 F1" tabindex="0">
<table>
<caption>逐段起音 F1，GuitarSet，2026年10月2日</caption>
<thead>
<tr><th scope="col">录音</th><th scope="col" class="num">Basic Pitch</th><th scope="col" class="num">Mirelo</th></tr>
</thead>
<tbody>
<tr><th scope="row">00_BN3-154-E_solo</th><td class="num">0.809</td><td class="num">0.814</td></tr>
<tr><th scope="row">01_Rock2-85-F_solo</th><td class="num">0.869</td><td class="num">0.326</td></tr>
<tr><th scope="row">02_BN1-147-Gb_solo</th><td class="num">0.860</td><td class="num">0.667</td></tr>
<tr><th scope="row">03_SS1-68-E_solo</th><td class="num">0.782</td><td class="num">0（无音符）</td></tr>
<tr><th scope="row">04_SS3-84-Bb_solo</th><td class="num">0.821</td><td class="num">0（无音符）</td></tr>
<tr><th scope="row">05_SS1-68-E_solo</th><td class="num">0.768</td><td class="num">0（无音符）</td></tr>
<tr><th scope="row">00_BN3-154-E_comp</th><td class="num">0.685</td><td class="num">0.810</td></tr>
<tr><th scope="row">01_SS1-68-E_comp</th><td class="num">0.622</td><td class="num">0.854</td></tr>
<tr><th scope="row">02_SS1-100-C#_comp</th><td class="num">0.567</td><td class="num">0.491</td></tr>
<tr><th scope="row">03_SS3-98-C_comp</th><td class="num">0.731</td><td class="num">0.745</td></tr>
<tr><th scope="row">04_Rock3-117-Bb_comp</th><td class="num">0.683</td><td class="num">0.385</td></tr>
<tr><th scope="row">05_Rock2-85-F_comp</th><td class="num">0.727</td><td class="num">0.564</td></tr>
</tbody>
</table>
</div>
<p>Basic Pitch 的 0.744，与作者在他们自己的 GuitarSet 测试集（72 段录音）上报告的结果相近：起音 F1 为 0.79，算上结束点为 0.56。两个引擎的速度也不同。Basic Pitch 在一台 Mac 上用 54 秒处理完了 433 秒的音频，这并不代表正式上线环境的耗时；第一段录音它用了 6.1 秒，Mirelo 用了 37 秒。</p>

<h2 id="full-band-recording">换成整支乐队会怎样？</h2>
<p>混音更难扒，这一回是 Mirelo 领先。我们取了 <a href="https://zenodo.org/records/4599666">Slakh2100</a> 测试集中 Track01881 的前 60 秒。Slakh2100 收录了 2,100 首歌，都是用基于采样的虚拟乐器从 MIDI 渲染而成，所以参考音符分毫不差。这一分钟里，钢弦吉他、爵士吉他、钢琴、贝斯、颤音琴和鼓都有演奏。把所有有音高的乐器合在一起算、不管音符被分给了哪件乐器，并以两个引擎各自返回的 MIDI 文件打分，Basic Pitch 的起音 F1 为 0.520，Mirelo 为 0.672。</p>
<p>Basic Pitch 只输出一个合并的声部，也不写鼓。Mirelo 为每件乐器各写一个声部，所以只有 Mirelo 能按乐器打分，这里按 General MIDI 的乐器类别分组：</p>
<figure class="bars-figure">
<div class="bars bars-wide" aria-hidden="true">
<p class="bars-title">Mirelo 各乐器的起音 F1 <span>一段 60 秒乐队片段，0 到 1</span></p>
<div class="bar-row"><span>钢琴</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="82.7%" height="10" rx="3"/></svg><b>0.827</b></div>
<div class="bar-row"><span>鼓</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="69.8%" height="10" rx="3"/></svg><b>0.698</b></div>
<div class="bar-row"><span>吉他</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="68.1%" height="10" rx="3"/></svg><b>0.681</b></div>
<div class="bar-row"><span>贝斯</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="45.5%" height="10" rx="3"/></svg><b>0.455</b></div>
<div class="bar-row"><span>颤音琴</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="13.7%" height="10" rx="3"/></svg><b>0.137</b></div>
</div>
<figcaption>钢琴和鼓扒得最好；颤音琴大多被漏掉了。参考音符数量见下表。</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Mirelo 在 Slakh2100 片段上各乐器的起音 F1" tabindex="0">
<table>
<caption>Mirelo 各乐器类别得分，Slakh2100 Track01881，前 60 秒</caption>
<thead>
<tr><th scope="col">乐器类别（参考音符数）</th><th scope="col" class="num">起音 F1</th></tr>
</thead>
<tbody>
<tr><th scope="row">钢琴（178）</th><td class="num">0.827</td></tr>
<tr><th scope="row">吉他：钢弦和爵士（334）</th><td class="num">0.681</td></tr>
<tr><th scope="row">贝斯（127）</th><td class="num">0.455</td></tr>
<tr><th scope="row">颤音琴（64）</th><td class="num">0.137</td></tr>
<tr><th scope="row">鼓点（423）</th><td class="num">0.698</td></tr>
</tbody>
</table>
</div>
<p>要把两把吉他区分开就更难了。按 10月2日 Mirelo 返回的音符列表逐声部打分，钢弦吉他为 0.364，爵士吉他为 0.148；MIDI 文件里的音符比这份列表早约 20 毫秒开始，所以按 MIDI 算出的数字略有不同。Mirelo 还给歌曲里根本没有的乐器报了 92 个音：管风琴、合成器铺底音色（pad）、小提琴和大提琴。鼓点只按时间打分，不管是哪面鼓。这只是一首歌，而且是渲染出来的，不是真实录音，所以只能看个大致方向，不能当排名。</p>

<h2 id="which-notes-are-wrong">哪些音最可能扒错？</h2>
<p>对 Basic Pitch 来说，是它以低力度写出的那些音。Basic Pitch 把每个音符的 MIDI 力度设为 127 乘以模型在这个音符上的平均激活值，所以力度同时也是一种置信度。它为 12 段吉他录音一共写了 2,715 个音符，按同样的 50 毫秒窗口匹配，低力度的音大多是错的：</p>
<figure class="bars-figure">
<div class="bars bars-wide" aria-hidden="true">
<p class="bars-title">Basic Pitch 扒对的音符比例，按力度分组 <span>12 段吉他录音中的 2,715 个音符</span></p>
<div class="bar-row"><span>低于 50</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="21%" height="10" rx="3"/></svg><b>21%</b></div>
<div class="bar-row"><span>50–59</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="40%" height="10" rx="3"/></svg><b>40%</b></div>
<div class="bar-row"><span>60–79</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="67%" height="10" rx="3"/></svg><b>67%</b></div>
<div class="bar-row"><span>80–127</span><svg class="bar" focusable="false"><rect class="bar-track" width="100%" height="10" rx="3"/><rect width="86%" height="10" rx="3"/></svg><b>86%</b></div>
</div>
<figcaption>Basic Pitch 写出的音力度越低，越可能是错的。具体数量见下表。</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Basic Pitch 音符力度与正确率的关系" tabindex="0">
<table>
<caption>Basic Pitch 力度与正确率，共 2,715 个音符</caption>
<thead>
<tr><th scope="col">力度</th><th scope="col" class="num">音符数</th><th scope="col" class="num">占比</th><th scope="col" class="num">正确率</th></tr>
</thead>
<tbody>
<tr><th scope="row">低于 50</th><td class="num">247</td><td class="num">9.1%</td><td class="num">21%</td></tr>
<tr><th scope="row">50–59</th><td class="num">250</td><td class="num">9.2%</td><td class="num">40%</td></tr>
<tr><th scope="row">60–79</th><td class="num">872</td><td class="num">32.1%</td><td class="num">67%</td></tr>
<tr><th scope="row">80–127</th><td class="num">1,346</td><td class="num">49.6%</td><td class="num">86%</td></tr>
</tbody>
</table>
</div>
<p>用力度来区分音符对错，AUC 为 0.77（0.5 相当于抛硬币，1.0 为完美）；换成音符时长只有 0.53，所以音短并不是有用的预警信号。把力度低于 50 的音全部删掉，总体起音 F1 能从 0.744 升到 0.765，可这些音里每五个就有一个是对的。所以 ScoreStarling 的乐谱检查只把它们列为存疑，而不是直接删除，最后交给你的耳朵判断。如果你自己运行 Basic Pitch，在钢琴卷帘里按力度给音符着色或排序，也能得到同样的待查清单。</p>

<h2 id="accuracy-claims-disagree">为什么各家公布的准确率对不上？</h2>
<p>因为它们在不同的录音上测的是不同的东西，有的甚至没说明测的是什么。</p>
<div class="table-scroll" role="region" aria-label="公开的准确率数据及其测试对象" tabindex="0">
<table>
<caption>公开数据，2026年10月3日核对</caption>
<thead>
<tr><th scope="col">来源</th><th scope="col">数据</th><th scope="col">测试对象</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://arxiv.org/abs/2203.09893">Basic Pitch 论文</a>（Bittner 等，2022）</th><td>起音 F1 0.79；算上结束点为 0.56</td><td>GuitarSet 测试集，72 段录音</td></tr>
<tr><th scope="row"><a href="https://arxiv.org/abs/2607.08168">MuScriptor 论文</a>（Rouard 等，2026）</th><td>起音 F1 60.4（百分制），YourMT3+ 为 32.52</td><td>作者自有数据中 372 段经过精细标注的真实录音</td></tr>
<tr><th scope="row"><a href="https://scorecloud.com/learn/how-accurate-is-automatic-music-transcription/">ScoreCloud 指南</a></th><td>“音高准确率通常在 85-95%”</td><td>独奏乐器和人声；未说明方法</td></tr>
<tr><th scope="row">本次测试</th><td>起音 F1：Basic Pitch 0.744，Mirelo 0.471</td><td>12 段 GuitarSet 录音</td></tr>
</tbody>
</table>
</div>
<p>只有指标、容差和录音都一致时，数字才有可比性。我们测出的 Basic Pitch 成绩可以和它论文里的放在一起看，因为两者都是在 GuitarSet 上算起音 F1，只是子集不同；但不能和 MuScriptor 的放在一起比。ScoreCloud 的页面没说音高准确率是怎么算的、测了多少段录音，也没说时间是否计入。MuScriptor 是一个开放的研究模型，Mirelo 的 Audio-to-MIDI Pro 就是从它发展而来的，Mirelo 称其正式上线的模型更准确。Mirelo 在 2026年9月17日发布的文章中说，更新后的模型“在我们的评估中”提高了音符和乐器识别的准确率，但没有公布具体数字。</p>

<h2 id="accurate-notes-readable-score">音符准了，就能得到好读的乐谱吗？</h2>
<p>不能。起音 F1 比的是以秒计时的音符，完全不管写出来的乐谱好不好读。一份乐谱还需要调号、合理的升降号、写出来的休止符、声部和拍号，而 Basic Pitch 这类模型输出的纯 MIDI 一样都没有。基于同样的 Basic Pitch 音符，我们最初的记谱程序在全部 12 个案例中写出的乐谱都没有调号，也看不到休止符；MuseScore Studio 的 MIDI 导入功能则在 12 个案例中有 10 个写出了标注的调，并显示了所有休止符。Mirelo 自己输出的 MusicXML，在它完成的 9 个案例中有 5 个写出了标注的调，而且全都记在单行高音谱表上。</p>
<p>扒谱得分高，乐谱照样可能难读；页面整洁，也照样可能藏着错音。完整的前后对比见《<a href="https://scorestarling.com/zh-hans/blog/why-midi-imports-look-messy">为什么 MIDI 导入 MuseScore 后乱成一团</a>》。</p>

<h2 id="which-engine-to-use">该选哪个扒谱引擎？</h2>
<p>看录音来选。以下是我们对这些结果的解读：</p>
<ul>
<li>单件乐器或单条旋律：选 Basic Pitch。它在吉他独奏上遥遥领先，免费开源，而且<a href="https://github.com/spotify/basic-pitch">它的 README</a> 也说一次只处理一件乐器时效果最好。</li>
<li>乐队，或者需要分出各个声部或鼓的混音：选 Mirelo 这类多乐器模型，因为 Basic Pitch 只会写出一个合并的声部。</li>
<li>吉他伴奏：两个都行。它们的平均分接近，在不同录音上各有胜负；如果选哪个很重要，就拿一小段两个都试试。</li>
<li>人声：我们还没有测。钢琴：我们只在合成音频上测过自己的钢琴独奏选项，所以没法给钢琴扒谱引擎排名。</li>
</ul>
<p>无论用哪个引擎，节奏和音符时值都要用耳朵再核对一遍。上面 10月2日的测量和 10月4日的重测都不包括我们的钢琴独奏选项。ScoreStarling 扒单件乐器或人声用 Basic Pitch，扒钢琴独奏用一个钢琴模型，两者都免费；扒乐队用 Mirelo，需要消耗积分（2026年10月7日核实）。每种都只处理上传文件的前五分钟，文件最大 100 MiB。人声或单件乐器默认按 4/4 拍记谱；钢琴独奏会自己判断拍号；乐队乐谱的拍号由演奏推断，或在开始前选定。吉他、贝斯和尤克里里声部还可以显示为 TAB 谱，但不记推弦、滑音和击弦；详见<a href="https://scorestarling.com/zh-hans/how-it-works#specs">规格说明</a>。</p>


<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://zenodo.org/records/3371780">GuitarSet 1.1.0</a>——Xi、Bittner、Pauwels、Ye 和 Bello，Zenodo（CC BY 4.0）</li>
<li><a href="https://guitarset.weebly.com/">GuitarSet 项目主页</a>——GuitarSet 作者团队</li>
<li><a href="https://github.com/marl/GuitarSet/issues">GuitarSet 的 issue 列表</a>——GitHub 上的 marl/GuitarSet</li>
<li><a href="https://zenodo.org/records/4599666">Slakh2100</a>——Manilow、Wichern、Seetharaman 和 Le Roux，Zenodo（CC BY 4.0）</li>
<li><a href="https://arxiv.org/abs/1909.08494">Cutting Music Source Separation Some Slakh</a>——Manilow 等，WASPAA 2019</li>
<li><a href="https://arxiv.org/abs/2203.09893">A Lightweight Instrument-Agnostic Model for Polyphonic Note Transcription and Multipitch Estimation</a>——Bittner 等，Spotify，2022</li>
<li><a href="https://github.com/spotify/basic-pitch">Basic Pitch</a> 及其 <a href="https://github.com/spotify/basic-pitch/blob/main/basic_pitch/note_creation.py">note_creation.py</a>——Spotify，GitHub</li>
<li><a href="https://mir-eval.readthedocs.io/latest/api/transcription.html">mir_eval.transcription</a>——mir_eval 文档</li>
<li><a href="https://arxiv.org/abs/2607.08168">MuScriptor: An Open Model for Multi-Instrument Music Transcription</a>——Rouard 等，2026</li>
<li><a href="https://mirelo.ai/blog/introducing-audio-to-midi-pro">Audio-to-MIDI Pro: full mixes to editable MIDI</a>——Mirelo，2026年9月17日</li>
<li><a href="https://mirelo.ai/models/audio-to-midi">Audio to MIDI converter</a> 和 <a href="https://mirelo.ai/changelog">Changelog</a>——Mirelo</li>
<li><a href="https://scorecloud.com/learn/how-accurate-is-automatic-music-transcription/">How Accurate Is Automatic Music Transcription?</a>——ScoreCloud</li>
</ol>
</section><h2>常见问题</h2><h3>AI 能把 MP3 扒成谱吗？</h3><p>能，可以逐个音符扒成 MIDI，只是会有错要改。Spotify 的 Basic Pitch 这类模型最擅长单件乐器；完整的混音需要多乐器模型，准确率也更低。在我们 2026年10月2日的测试中，成绩最好的一组是 Basic Pitch 扒吉他独奏，起音 F1 为 0.818（ScoreStarling 对它的用法做了两处调整后，10月4日重测为 0.846），所以还是要准备凭耳朵改掉一些音。</p><h3>Basic Pitch 的准确率够不够出乐谱？</h3><p>单件乐器出个初稿，够了。在 12 段吉他录音上，只看音符起点时它的起音 F1 为 0.744，把音符结束点也算上就只剩 0.508；而且它输出的 MIDI 没有调号、休止符和声部，得靠记谱这一步补上。《<a href="https://scorestarling.com/zh-hans/blog/why-midi-imports-look-messy">为什么 MIDI 导入 MuseScore 后乱成一团</a>》一文讲了这一步会改动什么。</p><h3>怎样自己测扒谱的准确率？</h3><p>你需要一份信得过的参考标注，音符时间以秒为单位。有了它，就可以用开源的 mir_eval 库，按我们用的容差给音符匹配打分：起点 50 毫秒，音高 50 音分，结束点为音符时长的 20% 或 50 毫秒。没有参考标注的话，就把短乐段循环播放，凭耳朵对照乐谱和录音。</p><h3>为什么 Mirelo 扒某些吉他独奏时一个音都没出？</h3><p>我们也不知道。2026年10月2日，六段独奏录音中有三段返回了空结果。当时我们没有提供 Mirelo 的可选乐器列表，之后也没有重试。在伴奏上，它的成绩接近 Basic Pitch；在多乐器片段上，它领先。</p><h3>AI 扒人声或钢琴准不准？</h3><p>人声方面，我们暂时还给不出自己的数据。钢琴方面，只在合成音频上测过：25 首乐曲的开头几个小节，每首以弹性速度演奏两遍（共 50 段演奏，2026年10月6日测量）。我们基于 Kong 等人钢琴模型的钢琴独奏选项，识别出了约 92% 的谱面音符，但 50 段里只有 35 段的拍子找对了，拍子、拍号和弱起小节全对的只有 26 段。Basic Pitch 的作者把它设计成适用于各种乐器，也包括人声。无论用哪个模型，清晰、干声的单人声或单件乐器录音，都最有机会扒准。</p>]]></content:encoded>
    </item>
    <item>
      <title>MIDI 导入 MuseScore 为什么乱糟糟？怎么解决</title>
      <link>https://scorestarling.com/zh-hans/blog/why-midi-imports-look-messy</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/why-midi-imports-look-messy</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>研究</category>
      <description>MIDI 存的是演奏，不是乐谱。本文用 12 段吉他转谱说明这会造成什么问题、MuseScore 为什么会重新猜调和速度，以及能解决大部分问题的预处理方法。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>MIDI 文件记录的是一次演奏：按了哪个键、什么时候按、按多重、按多久。它的音符没有升降号、声部、书面休止符或符杠，文件里也常常没有调号，所以 MuseScore 只能去猜，而音符偏离节拍网格时，它猜得最离谱。在 12 段吉他转谱上，我们最早的转换器一个调号也没写，还藏起了所有休止符；同样的音符先对齐到十六分音符网格、使用固定速度、在每条音轨写入调号，再交给 MuseScore Studio 4.7.5 的导入器，12 份里有 10 份调号正确，所有休止符也都显示了出来。MuseScore Studio 4 没有 MIDI 导入面板，所以这些修正要么写进 MIDI 文件，要么写进命令行用的选项文件。</p>
</section>

<h2 id="what-midi-does-not-store">MIDI 文件不存哪些信息？</h2>
<p>读谱所需的大部分信息，它都不存。一个 MIDI 音符就是 0 到 127 之间的一个键号加一个力度值，在音符开始时发送一次，结束时再发送一次（<a href="https://midi.org/summary-of-midi-1-0-messages">MIDI 协会</a>）。63 号音既是 D♯ 也是 E♭，该写哪个取决于调性，而音符本身并不说明。标准 MIDI 文件可以用<a href="https://mido.readthedocs.io/en/latest/meta_message_types.html">元事件</a>加入速度、拍号和调号，但声部、休止符、符杠和连音都没有标准的存放位置。休止符只是一段空隙，音长也只是 tick 数，而不是书面时值。</p>
<p>音频转 MIDI 工具还会带来第二个问题：它们的时间来自录音，音符很少正好落在拍点上。ScoreStarling 使用的 Spotify Basic Pitch 会按你给定的速度，把所有音符写进同一个 Electric Piano 1（电钢琴 1）声部，而且不写调号（<a href="https://github.com/spotify/basic-pitch/blob/main/basic_pitch/note_creation.py">note_creation.py</a>）。要把它变成乐谱，就得为每个音符选定音名写法、书面时值、声部和谱表。每个转换器都得做这些选择，区别只在于做得好不好。</p>

<h2 id="simple-converter-results">简单的转换器把 12 段吉他转谱弄成了什么样</h2>
<p>我们最早的记谱环节，是一个基于 music21 库自己写的转换器。我们把公开数据集 GuitarSet 中 12 段录音经 Basic Pitch 转出的 MIDI 交给它，同时提供每段录音的速度，再由 Verovio 原样排版它写出的内容。逐一检查这 12 份乐谱，我们发现：</p>
<ul>
<li>所有乐谱都看不到休止符：music21 的 MusicXML 导出器默认用隐藏的休止符填补空隙（<a href="https://github.com/cuthbertLab/music21/blob/master/music21/musicxml/m21ToXml.py">m21ToXml.py</a>）。</li>
<li>所有乐谱都没有调号。E 大调的曲子一个升号都没有；一段 C 大调伴奏的 431 个书面音符里，有 153 个带着多余的还原号（对应 421 个 MIDI 音符，有些被拆开后用延音线相连）。</li>
<li>音名写法只看音符编号。MIDI 63 号音一律写成 E♭，即使在本该写 D♯ 的 E 大调里也是如此；在一首 G♭ 大调的曲子里，49 个调内音中有 30 个写错了。</li>
<li>一行谱表上最多五个声部。音符的起点和终点各自对齐到最近的十六分音符，于是下一个音弹出时仍在振动的那根弦，就逼出了另一个声部，还配上了相应的附点和延音线。</li>
<li>吉他被写成了钢琴：12 份乐谱中有 10 份用钢琴大谱表，另外 2 份用低音谱号，没有一份用吉他谱号，因为 Basic Pitch 把所有音符都标成了 Electric Piano（电钢琴）。</li>
</ul>
<p class="note">2026年10月7日，我们用同一批保存下来的 MIDI 文件重新运行了这个转换器，得到的数字完全一样。</p>
<p>问题不在音乐字体。Verovio 和它的 Leipzig 字体只是照着 MusicXML 画；错的是 MusicXML 本身。</p>

<h2 id="before-and-after">前后对比：换用 MuseScore Studio 的导入器</h2>
<p>我们先看了开源的替代方案：partitura 能处理调性、音名写法和声部，但没有量化器，也不写休止符；我们找到的其他研究工具，要么只支持钢琴，要么没有声明许可证、仅限非商业用途，或者尚未发布。于是我们把这项工作交给了 MuseScore Studio 4.7.5 的 MIDI 导入器，以独立程序的方式运行。同样的 12 段录音、同样的 Basic Pitch 音符、同样的给定速度，按下文的方法预处理，2026年10月2日在一台 Mac 上测得：</p>
<div class="table-scroll" role="region" aria-label="12 段 GuitarSet 转谱的排版前后对比" tabindex="0">
<table>
<caption>12 段 GuitarSet 转谱的前后对比</caption>
<thead>
<tr><th scope="col">指标</th><th scope="col" class="num">music21 转换器</th><th scope="col" class="num">MuseScore 导入器</th></tr>
</thead>
<tbody>
<tr><th scope="row">调号与标注一致</th><td class="num">0/12</td><td class="num">10/12</td></tr>
<tr><th scope="row">调内音写法与调性相悖</th><td class="num">2,703 个中 322 个</td><td class="num">3,213 个中 0 个</td></tr>
<tr><th scope="row">每个书面音符的变音记号数</th><td class="num">0.53</td><td class="num">0.06</td></tr>
<tr><th scope="row">可见的休止符</th><td class="num">1,626 个中 0 个</td><td class="num">全部 988 个</td></tr>
<tr><th scope="row">单行谱表最多声部数</th><td class="num">5</td><td class="num">2</td></tr>
<tr><th scope="row">所有 MIDI 音符均写出</th><td class="num">12/12</td><td class="num">12/12</td></tr>
<tr><th scope="row">4/4 拍、使用给定速度</th><td class="num">12/12</td><td class="num">12/12</td></tr>
</tbody>
</table>
</div>
<p>两边的书面音符总数不同，是因为 MuseScore 用延音线跨拍、跨小节线连接音符的方式不一样。两个错的调都和标注差一个五度：G♭ 大调的曲子写成了 D♭ 大调，E 大调的写成了 B 大调。文件里没有我们估出的调时，MuseScore 自己猜的调只对了 5 份（2026年10月7日重测）；正是把估计结果写进文件，才把它提高到 10 份。导入器处理每段录音用时 1.2–4.3 秒，平均 2.3 秒。</p>

<h2 id="why-musescore-reguesses">MuseScore 为什么会重新猜速度、拍号和调？</h2>
<p>因为在它看来，没量化的 MIDI 像是一段现场演奏。导入器会检查落在拍点附近的音符，如果其中正好从拍点开始的不到 60%，它就把文件当作真人演奏（<a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_quant.cpp">importmidi_quant.cpp</a>）。接着它会运行自己的节拍追踪，选定拍号，并拉伸音符的时间去贴合它找到的拍子。MuseScore 的旧版手册说，这种模式是拿准确性换可读性，对没有对齐的文件默认开启。</p>
<p>调性另有一条规则。只要有任何一条非鼓音轨缺少调号，或者文件被判定为演奏，导入器就会根据音符估计调性（<a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_key.cpp">importmidi_key.cpp</a>）。所以只在速度音轨里写调号是不够的，每条有音符的音轨都要有。直接导入 Basic Pitch 的原始 MIDI 时，我们看到拍号、速度和调全都被重新猜了一遍，哪怕已经把“真人演奏”选项设为 false。</p>

<h2 id="prepare-midi">导入前该怎么处理 MIDI？</h2>
<p>能在 MIDI 文件里定下的记谱决定，就在 MuseScore 看到它之前定好。我们对每份转谱都会这样做：</p>
<ol>
<li>使用一个固定的速度。ScoreStarling 会用你设定的 BPM，或从录音中检测出的速度；在 DAW（编曲软件）里，可以跟着节拍器录音，或者把工程速度调到与这条录音一致。</li>
<li>把每个音符的起点和终点都移到网格上，网格精度就是你想读到的最短时值。本文 10月2日的测试用的是 4/4 拍下的十六分音符。所有音符都在网格上时，MuseScore 会把文件当作写好的乐谱来读，而不是当作演奏。</li>
<li>把速度和拍号放在第一条音轨里，每条有音符的音轨都写上调号。我们用 music21 的 Krumhansl–Schmuckler 调性识别算法根据音符估计调性；如果你知道真正的调，就直接用它。</li>
<li>给每条音轨设上真实的乐器。MuseScore 会根据 General MIDI 音色来选择谱表和谱号：仍标为 Electric Piano 的吉他会被放到钢琴大谱表上，而设为尼龙弦吉他音色时，会用下方带 8 的高音谱号。</li>
<li>注意同音反复。如果同一个音在一个网格单位内弹了两下，对齐后两次起音会合并成一次；我们会把第二次往后挪一个三十二分音符。</li>
</ol>

<h2 id="musescore-import-settings">MuseScore 导入该用哪些设置？</h2>
<p>MuseScore Studio 4 没有可以设置这些选项的导入面板。它的手册说 MIDI 导入面板“目前尚未实现”，要求恢复它的 GitHub issue（<a href="https://github.com/musescore/MuseScore/issues/11918">#11918</a>，2022年6月提出）在我们 2026年10月3日查看时仍未关闭。在软件里，唯一与量化有关的设置是导入 MIDI 文件时的最短音符时值，位于“首选项 → 导入”（Preferences → Import）。</p>
<p>导入器仍会从命令行里用 <code>-M</code> 传入的文件读取主要选项，<a href="https://handbook.musescore.org/appendix/command-line-usage">手册的命令行页面</a>附有一个示例。下面是 10月2日测试用的文件；现在 ScoreStarling 会把旋律写在一个声部里（<code>VoiceCount</code> 设为 0），并在第一小节是弱起小节时打开 <code>RecognizePickupBar</code>：</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;MidiOptions&gt;
  &lt;QuantValue&gt;2&lt;/QuantValue&gt;
  &lt;VoiceCount&gt;1&lt;/VoiceCount&gt;
  &lt;Duplets&gt;false&lt;/Duplets&gt;
  &lt;Triplets&gt;false&lt;/Triplets&gt;
  &lt;Quadruplets&gt;false&lt;/Quadruplets&gt;
  &lt;Quintuplets&gt;false&lt;/Quintuplets&gt;
  &lt;Septuplets&gt;false&lt;/Septuplets&gt;
  &lt;Nonuplets&gt;false&lt;/Nonuplets&gt;
  &lt;HumanPerformance&gt;false&lt;/HumanPerformance&gt;
  &lt;MeasureCount2xLess&gt;false&lt;/MeasureCount2xLess&gt;
  &lt;SplitStaff&gt;false&lt;/SplitStaff&gt;
  &lt;ClefChanges&gt;false&lt;/ClefChanges&gt;
  &lt;SimplifyDurations&gt;true&lt;/SimplifyDurations&gt;
  &lt;ShowStaccato&gt;false&lt;/ShowStaccato&gt;
  &lt;DottedNotes&gt;true&lt;/DottedNotes&gt;
  &lt;RecognizePickupBar&gt;false&lt;/RecognizePickupBar&gt;
  &lt;Swing&gt;0&lt;/Swing&gt;
&lt;/MidiOptions&gt;</code></pre>
<p>然后进行转换，输出文件用 MusicXML 扩展名：</p>
<pre><code>mscore -M midi-import.xml -o score.musicxml performance.mid</code></pre>
<p>程序的名称和位置因系统而异，Windows 上手册用的是 MuseScore4.exe。主要选项的作用如下：</p>
<div class="table-scroll" role="region" aria-label="MuseScore 的 MIDI 导入选项及其作用" tabindex="0">
<table>
<caption>我们的 MIDI 导入选项</caption>
<thead>
<tr><th scope="col">选项</th><th scope="col">取值</th><th scope="col">作用</th></tr>
</thead>
<tbody>
<tr><th scope="row">QuantValue</th><td>2</td><td>最短书面时值：十六分音符（1 为八分音符，3 为三十二分音符）</td></tr>
<tr><th scope="row">VoiceCount</th><td>1</td><td>每行谱表最多两个声部；声部数等于取值加一</td></tr>
<tr><th scope="row">Triplets 及其他连音选项</th><td>false</td><td>不识别连音，因为我们的网格里没有连音</td></tr>
<tr><th scope="row">HumanPerformance</th><td>false</td><td>不把文件当作现场演奏；只有音符都在网格上时，这样设置才成立</td></tr>
<tr><th scope="row">SplitStaff</th><td>false，钢琴为 true</td><td>把钢琴声部按左右手分到两行谱表；对其他乐器，MuseScore 则会拆成单独的声部</td></tr>
<tr><th scope="row">SimplifyDurations</th><td>true</td><td>更少的休止符，更简单的时值</td></tr>
<tr><th scope="row">ClefChanges</th><td>false</td><td>谱表中途不插入小号的变换谱号</td></tr>
<tr><th scope="row">RecognizePickupBar</th><td>false</td><td>第一小节按完整小节处理</td></tr>
<tr><th scope="row">Swing</th><td>0</td><td>不检测摇摆节奏</td></tr>
</tbody>
</table>
</div>
<p>如果你的音乐里有三连音，就不要对齐到直拍网格，并保持 Triplets 开启；MuseScore 识别三连音的效果如何，我们还没测过。在 MuseScore 3 里，这些设置都放在一个导入面板中，列名有 Max. voices、Search tuplets 和 Is human performance 等。</p>

<h2 id="trade-offs">谱面更整洁，代价是什么？</h2>
<p>会损失一些准确性，主要在音符时值上。以下是我们测得或已知的取舍：</p>
<ul>
<li>MuseScore 把 26% 的音符写得比量化后的演奏更长，最多长出三个十六分音符，从来没有写短过；同时发声但长短不同的音符，也可能被写成同一个时值。在 ScoreStarling 里，播放和 MIDI 导出保留演奏的实际时间，不过旋律线会先清理一遍：同时弹下的几个音只留最强的那个，微弱的回声会去掉，持续的音在下一个音开始时结束。</li>
<li>调性是根据音符估计的，12 份里错了 2 份，每次都差一个五度。带七个升号或降号的调，会写成对应的、只有五个升降号的等音调。</li>
<li>10月2日这次吉他测试用的是直拍的十六分音符网格，表示不了三连音和摇摆节奏。ScoreStarling 转谱单件乐器或人声时依然如此，拍号从 4/4 开始；钢琴独奏则会跟随演奏的小节划分，可以选用 4/4、3/4、6/8 或 12/8。如果你自己准备文件，请写入真实的拍号。</li>
<li>MuseScore 会按自己的规则把钢琴和弦分给左右手，即使是两个音挨得很近的和弦，也可能被分到不同的谱表上。</li>
</ul>
<p>其中一部分可以在导入后整理。<a href="https://handbook.musescore.org/notation/rhythm-meter-and-measures/regroup-rhythms">工具 → 重组节奏</a>（Tools → Regroup rhythms）会改写选中的段落，让音符按拍子分组，节奏本身不变。<a href="https://handbook.musescore.org/notation/pitch/respell-pitches">工具 → 用升号/降号重新拼写音高</a>（Tools → Respell pitches with sharps / with flats）可以修改音名写法，不过手册说它是针对同一音高的音符设计的。调号错了，在导入前改 MIDI 文件，比导入后逐个改音符快得多。</p>

<h2 id="mp3-to-musescore">MuseScore 能把 MP3 转成乐谱吗？</h2>
<p>MuseScore Studio 不行：它能打开 MusicXML、MIDI 这类乐谱格式，但打不开音频。Muse Group 于 2026年8月17日在 MuseScore 网页平台上推出了 <a href="https://www.mu.se/posts/musescore-audio-score-features">Audio-to-Score 测试版</a>：支持钢琴独奏和原声吉他，MP3 文件最大 30 MB（约三分钟），免费，结果以 MSCZ 文件返回，供 MuseScore Studio 打开。我们还没有测试过。</p>
<p>另一条路就是本文讲的做法：先用音频转 MIDI 模型，再整理它的时间，最后交给 MuseScore 的导入器。ScoreStarling 转单件乐器或人声时就是这样，用的是 Basic Pitch；钢琴独奏则用一个钢琴模型，它转出的音符同时给出节拍和小节线；页面由 Verovio 绘制。现在，单音旋律会写在一行谱表的一个声部里，弹和弦的声部会把和弦保留在同一行谱表上，“乐谱设置 → 布局”还可以改变单声部录音的记谱方式。上面的测量数据来自 10月2日。10月4日，在旋律改为一次一个音、写在一行谱表上之后，我们重测了一遍：调号同样 12 份对了 10 份，3,158 个调内音里写法与调性相悖的为 0，357 个休止符全部显示。</p>
<p>你也可以在 ScoreStarling 里选择或拖入 MIDI 文件，直接得到可编辑的乐谱；鼓轨会被略去，整份乐谱只用一个拍号。录音只转写前五分钟，上传文件最大 100 MiB。PDF 免费，MusicXML 需要先解锁乐谱（一次性 15 积分）；其余细节见<a href="https://scorestarling.com/zh-hans/how-it-works#specs">技术规格</a>（2026年10月7日核对）。<a href="https://scorestarling.com/app">工作台</a>可以免费开始使用；注明日期的吉他和乐队测量结果，收录在《<a href="https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy">AI 扒谱到底有多准？我们实测了一下</a>》一文中。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://midi.org/summary-of-midi-1-0-messages">Summary of MIDI 1.0 Messages</a>——MIDI Association</li>
<li><a href="https://mido.readthedocs.io/en/latest/meta_message_types.html">Meta Message Types</a>——mido 文档</li>
<li><a href="https://github.com/spotify/basic-pitch/blob/main/basic_pitch/note_creation.py">basic_pitch/note_creation.py</a>——Spotify Basic Pitch，GitHub</li>
<li><a href="https://github.com/cuthbertLab/music21/blob/master/music21/musicxml/m21ToXml.py">music21/musicxml/m21ToXml.py</a>——music21，GitHub</li>
<li><a href="https://zenodo.org/records/3371780">GuitarSet 1.1.0</a>——Xi 等，Zenodo（CC BY 4.0）</li>
<li><a href="https://musescore.org/en/handbook/4/midi-import">MIDI import</a>——MuseScore 4 手册</li>
<li><a href="https://github.com/musescore/MuseScore/issues/11918">Restore MIDI import panel, issue #11918</a>——MuseScore，GitHub</li>
<li><a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_quant.cpp">importmidi_quant.cpp</a> 和 <a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_key.cpp">importmidi_key.cpp</a>——MuseScore 源代码，GitHub</li>
<li><a href="https://musescore.org/en/print/book/export/html/22918">MIDI import</a>——早期 MuseScore 手册（导入面板）</li>
<li><a href="https://handbook.musescore.org/appendix/command-line-usage">Command line usage</a> 及其中的<a href="https://musescore.org/sites/musescore.org/files/midi_import_options_0.xml">MIDI 导入选项示例文件</a>——MuseScore Studio 手册</li>
<li><a href="https://handbook.musescore.org/customization/preferences">Preferences</a>、<a href="https://handbook.musescore.org/file-management/file-export">File export</a>、<a href="https://handbook.musescore.org/notation/rhythm-meter-and-measures/regroup-rhythms">Regroup rhythms</a> 和 <a href="https://handbook.musescore.org/notation/pitch/respell-pitches">Respell pitches</a>——MuseScore Studio 手册</li>
<li><a href="https://www.mu.se/posts/musescore-audio-score-features">Hear It, Play It: MuseScore Introduces New Smart Audio Recognition Features</a>——Muse Group，2026年8月17日</li>
</ol>
</section><h2>常见问题</h2><h3>怎么把 MIDI 转成 MusicXML？</h3><p>在 MuseScore Studio 里打开 MIDI 文件，选择“文件 → 导出”（File → Export），格式选 MusicXML，默认导出压缩的 .mxl。也可以在终端里运行 mscore -o score.musicxml file.mid，效果相同；加上 -M 还能传入一个选项文件，设置量化和声部。记得先把 MIDI 处理好，否则 MusicXML 里也会是同样的一团乱。</p><h3>为什么 MIDI 导入后满是延音线和附点音符？</h3><p>因为真人演奏的音符很少刚好在拍点上结束。对齐到很细的网格后，它们会跨过拍子和小节线，只能用延音线连起来；互相重叠的音符还会溢出到额外的声部里。把量化精度设为你想读到的最短时值，把导入器限制为最多两个声部，并让它简化时值；这样 MuseScore 会把部分音符写得稍长一点，让谱面更整洁。</p><h3>为什么我以为是 E♭，MuseScore 却写成了 D♯？</h3><p>MIDI 只记录 63 号音，不记录它该写成哪个音名，所以 MuseScore 会按调号来决定写法；某条音轨没有调号时，它就自己估计调性。导入前，在每条音轨里写好正确的调号。导入后，可以用“工具 → 用升号/降号重新拼写音高”（Tools → Respell pitches with sharps / with flats）修改选中的同音高音符。</p><h3>MuseScore Studio 4 里还能用 MuseScore 3 的 MIDI 导入面板吗？</h3><p>截至 2026年10月3日还不行：这个面板没有移植过来，要求恢复它的 <a href="https://github.com/musescore/MuseScore/issues/11918">GitHub issue #11918</a> 也仍未关闭。MuseScore Studio 4 保留了一项针对导入 MIDI 的“最短音符”首选项，它的命令行也接受用 -M 传入选项文件，能覆盖原面板的主要设置。</p><h3>ScoreStarling 的转谱结果能在 MuseScore 里打开吗？</h3><p>可以。导出 MusicXML（.musicxml 或 .mxl），调号、音名写法、休止符和声部都会保留；如果只需要音符，导出 MIDI 也行。两者都需要先解锁乐谱，一次性花费 15 积分。ScoreStarling 可以免费开始使用；每份转谱结果都只是初稿，需要你用耳朵核对。</p>]]></content:encoded>
    </item>
    <item>
      <title>如何把语音备忘录转成乐谱</title>
      <link>https://scorestarling.com/zh-hans/blog/voice-memo-to-sheet-music</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/voice-memo-to-sheet-music</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>指南</category>
      <description>用稳定的速度录下一段人声，导出 .m4a 文件，填写 BPM 或交给系统自动检测，再用耳朵逐段核对初稿。本文附上录音技巧、操作步骤和目前的限制。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>单独录下旋律：只有一个人声或一件乐器，手机离得近，房间安静，速度稳定，最好戴着耳机听节拍器，这样就知道 BPM 是多少。导出这段录音（iPhone 默认导出 .m4a），剪掉开头，让它从第一个音开始，再连同速度一起交给扒谱工具。然后把得到的乐谱当作初稿：每次循环播放两小节，和录音对照，修正音高和时值，最后再移调，导出 PDF、MusicXML 或 MIDI。</p>
</section>

<h2 id="can-an-app-turn-humming-into-sheet-music">哼唱或唱歌能用 App 转成乐谱吗？</h2>
<p>可以，前提是录音里只有一条清楚的旋律。软件要做两件不同的事。首先是听出音符：每个音的音高，从什么时候开始，到什么时候结束。然后把这些音符写下来，也就是确定调、拍号、音符时值和小节线的位置。如果旋律还只在你脑子里，就先对着手机唱出来；下面说的一切都适用于这段录音。</p>
<p>在 ScoreStarling 里，单件乐器或人声的第一步由 Spotify 开源的 Basic Pitch 模型完成，它在我们的服务器上把音频转成 MIDI。Spotify 称 Basic Pitch 适用于“几乎任何乐器，包括人声”。这是他们的说法。我们自己目前测过的是吉他录音、一段乐队片段和合成的钢琴，没有测过人声。第二步交给 MuseScore Studio 4.7.5 的 MIDI 导入功能，最后由 Verovio 排版。</p>
<p>节奏往往是语音备忘录里最难处理的部分。ScoreStarling 会检测速度，或者使用你填写的 BPM，但对单件乐器或人声，它仍然从 4/4 拍开始、按均分的网格记谱，所以速度漂移、旋律前的空白或者速度不对，都可能让延音线和休止符放错位置。钢琴独奏会跟随演奏本身的律动和拍子，但它是给钢琴用的，不适合唱出来的录音。下面的建议能帮旋律保持稳定的律动。</p>

<h2 id="how-to-record-a-voice-memo-for-transcription">用来扒谱的语音备忘录该怎么录？</h2>
<p>给软件一条旋律：速度稳定，离麦克风近，没有别的声音同时在响。</p>
<ul>
<li>只录一个人声或一件乐器：不放伴奏，不要第二个人一起唱，人声下面也不要垫吉他。混音太满、混响太重、几件乐器同时演奏，都会让初稿更难看懂。</li>
<li>手机靠近一点，房间保持安静。Apple 的使用手册说，录音音量靠距离来调：把手机移近或移远声源。要近，但别近到最响的音会失真。有软装的房间比浴室或楼梯间回声小。</li>
<li>用一个你听得见、手机录不到的节拍：用另一台设备播放节拍器，戴耳机听。Apple 的使用手册说，如果 iPhone 上的其他 App 开始播放音频，“语音备忘录”就会停止录音，所以节拍器不能放在正在录音的这台手机上。</li>
<li>记下 BPM。如果录音时没开节拍器，事后可以用节拍器的 Tap 测速功能跟着点，或者数一数 15 秒内有多少拍，再乘以 4。</li>
<li>预备拍在心里数。说出口的“一、二、三、四”和其他声音没有区别，可能会被写成音符，所以请在心里或耳机里数，或者事后把预备拍剪掉。</li>
<li>用“da”或“doo”这样的音节来唱，不要闭着嘴哼，这样重复的音不会糊成一个。这是我们的建议，不是测出来的结论。</li>
<li>每段录音控制在 5 分钟以内：文件最大 100 MiB，只会转前 5 分钟。长的歌曲请分段录，每段都从小节线开始。</li>
<li>额外功能都别开。在 Pixel 9 及更新机型上，Google 的“录音机”可以用 AI 给录音加背景音乐，而加进去的音乐也会成为要转谱的声音。如果你用“语音备忘录”的分层录音在伴奏上唱过，先分离音层，只上传人声。</li>
</ul>
<p>然后剪掉开头。第 1 小节从文件的 0:00 开始，所以要把开头的空白或预备拍剪掉，让文件正好从第一个音开始。如果曲子是弱起，之后可以把第一小节标成弱起小节。在“语音备忘录”里，用编辑界面中的黄色修剪控制柄就能剪，双指张开可以放大波形，剪得更精确。Google“录音机”有“剪裁”功能，会另存一份剪好的副本。</p>

<h2 id="what-format-is-a-voice-memo">iPhone 和安卓的语音备忘录是什么格式？</h2>
<p>iPhone 的“语音备忘录”默认把录音导出为 .m4a 文件；安卓手机的录音机因厂商而异，分享出来之后看一下文件扩展名就知道了。</p>
<p>Apple 的《iPhone 使用手册》（iOS 27，2026年10月3日查阅）说，录音默认导出为 .m4a，分层录音会合并成一层，空间音频会变成立体声。要把文件从手机里导出来，在录音的共享选项里选“保存到‘文件’”。不要选“可编辑”：它导出的是 QuickTime 音频格式，供以后再调整效果和音层。如果是分层录音，先用“分离音层”，每个音层都会变成单独的 .m4a。</p>
<p>在 Pixel 手机上，Google“录音机”可以把录音作为音频文件分享：轻触并按住录音，然后依次选择“分享”“文件”“音频”。Google 的帮助页面没有写明是什么格式（2026年10月3日查阅），其他安卓品牌也各有自己的录音机 App。不管拿到的是什么格式，ScoreStarling 都支持 WAV、MP3、FLAC、M4A、OGG 和 AIFF。你也可以不用文件，直接在浏览器里录音；浏览器录音到 4:59 会停止，正好在 5 分钟的限制以内。</p>

<h2 id="voice-memo-to-sheet-music-steps">分步教程：在 ScoreStarling 里把语音备忘录转成乐谱</h2>
<p>上传录音，填上速度，转谱，然后在导出前用耳朵校对乐谱。在 ScoreStarling 上，转单件乐器或人声、编辑和下载 PDF 都免费；解锁乐谱的 MusicXML 和 MIDI 要用积分（2026年10月7日核实）。打开<a href="https://scorestarling.com/app">工作台</a>就可以开始。</p>
<ol>
<li>选择“新建乐谱”。把文件拖进来，或者选择“录音”，直接对着浏览器唱。在你点击“创建乐谱”之前，什么都不会保存。</li>
<li>选择演奏的是什么时，保留“单件乐器或人声”，它会在我们的服务器上免费运行 Basic Pitch。“钢琴独奏”也免费。“乐队或多件乐器”要用积分，每分钟 40 积分，开始前会先显示价格，音频会发送给我们的乐队转谱合作伙伴 Mirelo；只有你选了它才会运行。</li>
<li>打开“更多选项”，在“每分钟拍数”里填写以四分音符为一拍的速度（30 到 240），或者留在“自动”让系统检测。这个数值决定乐谱怎么写，不会改变录音。如果拿不准是 70 还是 140，选哪个都行：填 140 的话，每个音符都会写成两倍的时值。</li>
<li>点击“创建乐谱”。乐谱出来后，播放会和你的录音同步。</li>
<li>按下一节介绍的方法，每次两小节，用耳朵检查。</li>
<li>听到问题就改。选中一个音符，可以把它升高或降低半音（也可以用上下方向键）、修改时值，或者删除；删除后会留下休止符，拍子不会乱。改错了可以撤销、重做，或者回到已保存的版本。</li>
<li>最后在“乐谱设置”里收尾：调号和谱号、拍号，曲子如果在强拍之前就开始，还要设置弱起小节，以及按音程移调。移调放到最后：它会移动所有音符和调，但录音仍是原来的调，移调之后再用耳朵对照就难了。</li>
<li>下载你需要的文件：打印用 PDF，打谱软件用 MusicXML，DAW（编曲软件）用 MIDI，还有页面图片、分谱，以及 MP3 或 WAV 试听音频。MusicXML 和 MIDI 需要先解锁乐谱（一次性 15 积分）；解锁之前，PDF、页面图片和分谱的页脚会有一行小字。音频是用 General MIDI 音色合成的乐谱，不是你的声音。该选哪种文件，请看《<a href="https://scorestarling.com/zh-hans/blog/musicxml-vs-midi-vs-pdf">MusicXML、MIDI 和 PDF 有什么区别？你该用哪一种</a>》。</li>
</ol>

<h2 id="check-transcription-by-ear">怎样对照录音检查转谱结果？</h2>
<p>每次处理两小节：选中它们，打开循环播放，先用三种方式听一遍再动手改。播放器在播放时可以随时切换：只听乐谱、两者一起听、只听录音。先单独听录音，把旋律记牢，再单独听乐谱，最后两者一起听。音高错了会和你的声音打架；位置错了的音听起来像重复了一遍，好像回声。</p>
<p>按顺序修改。先检查小节线，因为第一拍错了，每个小节都会错：可以标出弱起小节，或者重新剪辑文件再转一次。然后改音高，再改时值，表情记号和排版留到最后。</p>
<div class="table-scroll" role="region" aria-label="语音备忘录检查清单" tabindex="0">
<table>
<caption>语音备忘录检查清单</caption>
<thead><tr><th scope="col">阶段</th><th scope="col">检查项</th><th scope="col">原因</th></tr></thead>
<tbody>
<tr><th scope="row">录音前</th><td>只有一个人声或一件乐器，没有其他声音</td><td>“单件乐器或人声”选项就是为单个声部设计的</td></tr>
<tr><th scope="row">录音前</th><td>用另一台设备在耳机里放节拍器；记下 BPM</td><td>自己填的 BPM 比自动估算更可靠</td></tr>
<tr><th scope="row">录音前</th><td>手机靠近，房间安静，回声小</td><td>噪音和混响会让初稿更难看懂</td></tr>
<tr><th scope="row">上传前</th><td>剪到从第一个音开始，时长不超过账号的上限</td><td>从清楚的拍点开始；每段最长 5 分钟</td></tr>
<tr><th scope="row">检查时</th><td>小节线和弱起小节</td><td>第一拍错了，每个小节都会跟着错位</td></tr>
<tr><th scope="row">检查时</th><td>重复音和长音</td><td>同音高的音可能合并；时值会为易读而简化</td></tr>
<tr><th scope="row">检查时</th><td>调号</td><td>它是根据音符估算的，可能差一个五度</td></tr>
<tr><th scope="row">导出前</th><td>最后再移调</td><td>录音始终保持原调</td></tr>
</tbody>
</table>
</div>

<h2 id="what-to-expect-first-draft">初稿会是什么样？</h2>
<p>你拿到的是一份需要修改的初稿，不是可以直接拿去演奏的成品。我们还没有测过人声，所以下面这些来自“单件乐器或人声”选项的工作方式，以及我们在公开数据集 GuitarSet 的 12 段吉他独奏和伴奏录音上的测量，测量分别在 2026年10月2日和 10月4日进行。</p>
<ul>
<li>“单件乐器或人声”会按检测到的或你填写的速度，从 4/4 拍开始按均分的网格记谱，不会推断三连音和摇摆。请检查节奏，必要时在“乐谱设置”里修改拍号。“钢琴独奏”能跟上 3/4、4/4、6/8 和 12/8 拍，但它是给钢琴用的。</li>
<li>音符的结尾没有开头可靠。10月2日在这些吉他录音上，Basic Pitch 的宏平均起音 F1（onset F1）为 0.744；如果连每个音的结尾也要对上，就只有 0.508。10月4日，我们开始先给跑调的录音校音、把旋律按一次一个音来写，之后重跑的结果为 0.763 和 0.522。起音 F1 的计算方式是：音高一致、起点误差在 50 毫秒以内，就算找到了这个音，再把漏掉的音和多出来的音放在一起权衡；1.0 为满分。具体方法见《<a href="https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy">AI 扒谱到底有多准？我们实测了一下</a>》。</li>
<li>跑调的录音会先校正。如果一段录音整体比 A440 稳定地偏高或偏低 10 音分以上，转谱前会先校音，让音符按标准音高记谱；音高飘忽不定的人声则保持原样。</li>
<li>写出来的时值会为了易读而简化。在这些吉他录音中，MuseScore Studio 把 26% 的音符写得比实际演奏的长（以对齐网格之后为准），最多长出三个十六分音符，从没有写短过。播放和导出的 MIDI 会保留你原本的节奏；如果希望谱面准确反映你唱的长度，就手动修改音符的时值。</li>
<li>重复音要留意。同一音高的两个音之间没有间隙时，模型很难把它们分开，所以要检查有没有把你唱的两个音记成了一个长音。修正方法：先把这个音缩短，再用同样的音高填上空出来的休止符。</li>
<li>调是猜出来的。它根据音符来估算；在那组吉他录音里，12 段中有 2 段猜错了，两次都差了一个五度。请在“调号与谱号”里设成正确的调。</li>
<li>检查旋律线。检测到的单音旋律会作为 Melody 声部写在一行谱表上：同时奏响的几个音只保留最强的一个，并去掉微弱的回声；而演奏和弦的声部会把和弦保留在同一行谱表上。对于单声部录音，也可以在“乐谱设置 → 布局”里给较早的乐谱应用“单行旋律”，它会使用现有的音符。多余的音符会从播放中去掉，“撤销”可以把它们恢复。请确认它保留的是你唱的那些音。</li>
<li>你只会得到音符。ScoreStarling 不会根据录音写出歌词或和弦标记。</li>
</ul>

<h2 id="write-a-melody-down-by-ear">不借助工具，怎样凭耳朵把旋律记下来？</h2>
<p>如果没有合适的工具，你也可以自己记谱：准备一个键盘（手机上的键盘 App 也行）和一个能循环播放片段的播放器。这样慢一些，但能练耳朵。</p>
<ol>
<li>找到“家”。哼出旋律最想落脚的那个音，在键盘上找到它。它通常就是这个调的主音，能提示你先试哪个调号。</li>
<li>找拍子。跟着录音用脚打拍子并数拍。每三拍出现一个强拍，可能是 3/4 拍；每四拍一个强拍，可能是 4/4 拍；如果每一拍都分成三份，可以考虑 6/8 拍。</li>
<li>先记节奏，一次一小节，在数拍的位置下面做记号。</li>
<li>再填音高。从一个你确定的音开始，判断下一个音是级进、小跳还是大跳，然后在键盘上核对。</li>
<li>把你记的版本和录音对照着播放，跟着一起唱。听着别扭的地方，就是要改的音。</li>
</ol>
<p>也有其他 App 能识别歌声。ScoreCloud 面向 iPhone 和 iPad 的 Express 应用能把唱歌、口哨或哼唱记成乐谱；Klangio 的 Sing2Notes 可以在浏览器以及 iOS 和安卓上使用，支持下载 PDF、MIDI 和 MusicXML（两者均依据其官网介绍，2026年10月3日查阅）。这两款我们都没有测试过。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://support.apple.com/guide/iphone/make-a-recording-iph4d2a39a3b/ios">Make a recording in Voice Memos on iPhone</a>——Apple 支持，《iPhone 使用手册》（iOS 27）</li>
<li><a href="https://support.apple.com/guide/iphone/export-a-recording-to-files-iph831c37815/ios">Export a Voice Memos recording to Files on iPhone</a>——Apple 支持</li>
<li><a href="https://support.apple.com/guide/iphone/add-a-second-layer-to-a-recording-iphfefa3252b/ios">Add a second layer to a Voice Memos recording on iPhone</a>——Apple 支持</li>
<li><a href="https://support.apple.com/guide/iphone/edit-or-delete-a-recording-iphc9bdaee83/ios">Edit or delete a recording in Voice Memos on iPhone</a>——Apple 支持</li>
<li><a href="https://support.google.com/pixelphone/answer/16267696?hl=en">Save &amp; share recordings &amp; transcripts</a>——Pixel 手机帮助，Google</li>
<li><a href="https://support.google.com/pixelphone/answer/16267367?hl=en">Create, edit &amp; delete a recording on your Pixel device</a>——Pixel 手机帮助，Google</li>
<li><a href="https://basicpitch.spotify.com/about">About Basic Pitch</a>——Spotify</li>
<li><a href="https://scorecloud.com/">ScoreCloud</a>——ScoreCloud</li>
<li><a href="https://klang.io/sing2notes/">Sing2Notes</a>——Klangio</li>
</ol>
</section><h2>常见问题</h2><h3>可以哼唱吗？还是得把歌词唱出来？</h3><p>两种方式都能给软件一条有音高的旋律去跟，不过我们都还没有测过。建议用辅音开头的音节来唱，比如“da”，这样每个音都有清楚的起点。同一个音高连续重复时，这一点最重要。</p><h3>它能自动算出速度吗？</h3><p>能。把“每分钟拍数”留在“自动”，系统就会检测速度。不过直接填写你录音时用的 BPM 更可靠：我们在 2026年10月3日用 14 段测试录音检查过，其中 6 段检测出的速度误差在 4% 以内；没测准的大多偏差 3:2 或 4:3，这是摇摆或三连音律动的典型情况。单件乐器或人声先按 4/4 拍、均分的节奏网格记谱；如果曲子是别的拍子，转谱后在“乐谱设置”里修改。</p><h3>能给旋律加上歌词或和弦标记吗？</h3><p>无法从录音里自动生成。转谱结果只有音符，没有歌词或和弦名。你可以解锁乐谱（一次性 15 积分），导出 MusicXML，再在 MuseScore Studio 等打谱软件里添加歌词或和弦标记。</p><h3>语音备忘录最长可以录多久？</h3><p>最长 5 分钟。更长的文件（最大 100 MiB）也能上传，但只转前 5 分钟；浏览器录音到 4:59 会自动停止。更长的曲子请在小节线处切开，分段转谱。</p><h3>上传的录音会被发送到哪里？</h3><p>默认的“单件乐器或人声”会在 ScoreStarling 自己的服务器上转谱，项目只对你的账号可见。只有选择“乐队或多件乐器”时，音频才会发送给我们的乐队转谱合作伙伴 Mirelo。存储和删除的规则见<a href="https://scorestarling.com/zh-hans/privacy">隐私政策</a>。</p>]]></content:encoded>
    </item>
    <item>
      <title>乐谱 MCP 服务器盘点：各自能做什么</title>
      <link>https://scorestarling.com/zh-hans/blog/sheet-music-mcp-servers</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/sheet-music-mcp-servers</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>技术</category>
      <description>我们在 2026年10月3日逐一查看了 26 个乐谱 MCP 服务器，涵盖录音转乐谱、乐谱渲染、操控 MuseScore 和 Dorico、用 music21 做音乐分析等用途。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>2026年10月3日，我们逐一查看了 26 个与乐谱相关的 MCP 服务器，它们做的是五类不同的事。两个托管型服务器能把录音转成乐谱：Mirelo 和我们自己的 ScoreStarling；audio2score-mcp 则用开源组件在你的电脑上完成这件事。MuseScore、Dorico 和 Finale 都有在你电脑上操控软件的服务器，其中仍在活跃更新的 MuseScore 服务器是 mcp-musescore 和 mcp-score。其余的服务器，有的渲染 AI 助手写出的乐谱，有的用 music21 等引擎分析音乐，还有的识别和查找印刷乐谱。</p>
</section>

<h2 id="how-we-checked">我们是怎么找到并核查这些服务器的？</h2>
<p>我们在官方 MCP Registry、GitHub、PulseMCP、Glama 和 mcp.so 上搜索了与乐谱、记谱、MuseScore、music21、MusicXML、ABC、LilyPond、Dorico 和音频转 MIDI 相关的服务器，再逐一打开这里列出的每个服务器的 README 或产品页面。光靠这些目录找到的不多：在 PulseMCP 上搜“sheet music”只有一个服务器，搜“music21”一个也没有，所以清单大部分来自 GitHub。</p>
<p>表格中的“最近更新”取最新的发布版本（附版本号），没有发布版本的则取主分支上的最新提交。“本地”表示服务器在你的电脑上运行，通常通过 stdio 通信；“ChatGPT”指的是它的开发者模式。我们给每个托管型服务器发了一条 MCP initialize 请求，也就是任何客户端发出的第一条消息，看它是否要求登录。本地服务器我们没有安装或运行，所以它们的功能以作者的描述为准。我们没有收录只生成 MIDI 或音频的服务器、DAW（编曲软件）控制器、流媒体和元数据服务器、练习类应用、其他项目的复制品，以及 README 中没有描述出可用服务器的代码仓库。ScoreStarling 是我们自己的产品，所以它也按同样的栏目列出，连同它的限制。</p>

<h2 id="recording-to-notation">哪些 MCP 服务器能把录音转成乐谱？</h2>
<p>在我们查看的服务器中，有三个能接收录音并返回乐谱：Mirelo 和 ScoreStarling 是托管型的，audio2score-mcp 在本地运行。另有三个只做到一半，不生成乐谱：audiolla 和一个基于 MuScriptor 的服务器返回 MIDI，fingerstyle-tab-mcp 输出文本格式的吉他六线谱（TAB）。</p>
<div class="table-scroll" role="region" aria-label="能把录音转成乐谱或 MIDI 的 MCP 服务器" tabindex="0">
<table>
<caption>录音转乐谱或 MIDI（2026年10月3日核查）</caption>
<thead>
<tr><th scope="col">服务器</th><th scope="col">功能</th><th scope="col">运行方式</th><th scope="col">登录</th><th scope="col">写明的客户端</th><th scope="col">最近更新</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://scorestarling.com/zh-hans/mcp">ScoreStarling</a>（我们自己的）</th><td>把录音、乐谱或记谱文本转成可编辑的乐谱；可修改、预览，支持五线谱/六线谱/简谱，可导出乐谱和音频文件。单件乐器或人声、钢琴独奏免费；乐队需要积分；录音只转前五分钟；文件最大 100 MiB；未指定速度时自动检测。<a href="https://scorestarling.com/zh-hans/how-it-works#specs">目前的限制</a>；<a href="https://scorestarling.com/zh-hans/mcp#webmcp">浏览器端的 WebMCP Beta</a> 单独提供</td><td>云端托管</td><td>OAuth；免费账号</td><td>Claude、ChatGPT、Claude Code、Codex、Cursor、VS Code、Gemini CLI、Devin Desktop、Hermes</td><td>2026年10月8日更新</td></tr>
<tr><th scope="row"><a href="https://mirelo.ai/mcp">Mirelo MCP</a></th><td>对录音做音频转 MIDI，返回 MIDI 和 MusicXML，自动检测速度；其余大多是音效工具</td><td>云端托管</td><td>OAuth</td><td>Claude、ChatGPT、Cursor、Claude Code、Codex</td><td>2026年7月30日上线</td></tr>
<tr><th scope="row"><a href="https://github.com/David7ce/audio2score-mcp">audio2score-mcp</a></th><td>先用 Basic Pitch 转成 MIDI，再用 music21 转成 MusicXML 文件</td><td>本地</td><td>无</td><td>Claude Code</td><td>2026年8月20日</td></tr>
<tr><th scope="row"><a href="https://github.com/psyb0t/docker-audiolla">audiolla</a></th><td>用 Basic Pitch 做音频转 MIDI，是几十个音频工具之一；不生成乐谱</td><td>自托管，HTTP</td><td>可选令牌</td><td>不限</td><td>v1.1.7，2026年8月1日</td></tr>
<tr><th scope="row"><a href="https://github.com/ALLLLLL703/midi">midi</a>（MuScriptor）</th><td>用 MuScriptor 模型把音频转成多乐器 MIDI；不生成乐谱</td><td>本地，Linux</td><td>无</td><td>未写明</td><td>2026年8月4日</td></tr>
<tr><th scope="row"><a href="https://github.com/blooper20/fingerstyle-tab-mcp">fingerstyle-tab-mcp</a></th><td>用 Demucs 和 Basic Pitch 把吉他录音转成文本六线谱</td><td>本地</td><td>无</td><td>Claude Desktop</td><td>2026年1月15日</td></tr>
</tbody>
</table>
</div>
<p>这两个托管型服务器的定位不同。Mirelo 的服务器大部分是音效工具；它的 Audio-to-MIDI 工具返回文件，未指定速度时会自动检测，而它插件的 README 中没有列出任何编辑乐谱的工具。ScoreStarling 同样会在你未指定速度时自动检测，并把乐谱保存在你的账号里，你可以在那里改错音、先试听修改再应用、移调和导出；它目前正在等待 ChatGPT 和 Claude 应用目录的审核。Mirelo 也是 ScoreStarling 里乐队转谱的合作伙伴。audio2score-mcp 有意只输出文件，把编辑留给你的打谱软件。MuScriptor 服务器需要登录 Hugging Face 才能下载模型，而模型权重仅限非商业用途。</p>

<h2 id="render-or-generate-notation">哪些 MCP 服务器能渲染或生成乐谱？</h2>
<p>有五个服务器能把 AI 助手以 ABC、LilyPond、JSON 或 MusicXML 写出的乐谱，变成你能看、能听或能打印的东西。它们都不能把录音转成乐谱。</p>
<div class="table-scroll" role="region" aria-label="能渲染或生成乐谱的 MCP 服务器" tabindex="0">
<table>
<caption>渲染或生成乐谱（2026年10月3日核查）</caption>
<thead>
<tr><th scope="col">服务器</th><th scope="col">功能</th><th scope="col">运行方式</th><th scope="col">登录</th><th scope="col">写明的客户端</th><th scope="col">最近更新</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server">Sheet Music Server</a>（MCP Apps 示例）</th><td>用 abcjs 把 ABC 记谱转成可播放的乐谱；只有一个工具</td><td>本地，或使用托管的演示版</td><td>无</td><td>支持 MCP Apps 的宿主应用</td><td>v2.0.3，2026年9月25日</td></tr>
<tr><th scope="row"><a href="https://github.com/linxule/mcp-music-studio">MCP Music Studio</a></th><td>把 ABC 转成乐谱，可播放、移调，可下载 MIDI 和 WAV；支持 Strudel 现场编程</td><td>云端托管或本地</td><td>无</td><td>Claude、Claude Code、Codex CLI、Gemini CLI、Cursor、VS Code、Windsurf</td><td>v0.9.2，2026年10月2日</td></tr>
<tr><th scope="row"><a href="https://github.com/delmas41/gradusnotation">Gradus Notation</a></th><td>把 JSON 乐谱转成 SVG、MusicXML 和 MIDI；和声分析；排版检查</td><td>本地，调用 Gradus 的 API</td><td>无</td><td>Claude Code、Claude Desktop</td><td>v0.8.0，2026年8月26日</td></tr>
<tr><th scope="row"><a href="https://github.com/wspringer/lilypond-mcp">lilypond-mcp</a></th><td>把 LilyPond 代码转成裁切好的 PDF、EPS、SVG 或 PNG</td><td>本地</td><td>无</td><td>Claude Code、Claude Desktop</td><td>v0.2.2，2026年9月9日</td></tr>
<tr><th scope="row"><a href="https://github.com/raulkivi/sheet-music-mcp">sheet-music-mcp</a></th><td>为合唱团准备的六个服务器：照片转 MusicXML、渲染、声部播放、ABC 编辑、版本差异比对、跟随演唱者</td><td>本地</td><td>无</td><td>Claude Desktop、Claude Code、Cursor、Windsurf、Continue、Zed</td><td>v0.2.1，2026年9月26日</td></tr>
</tbody>
</table>
</div>
<p>官方示例最精简：它只有一个工具 <code>play-sheet-music</code>，接收 ABC 并在支持 MCP Apps 的宿主应用里绘制出来；部署在 <code>example-server.modelcontextprotocol.io</code> 的一份副本无需登录就响应了我们的握手请求，Music Studio 的托管地址也一样。Gradus 是一个本地包，会把乐谱发送到开发者托管的 API（不需要密钥），并要求 AI 智能体在展示输出时注明出自 Gradus；它还能分析和声，并对照一本注明出处的规则书检查排版。lilypond-mcp 运行的是 WebAssembly 版的 LilyPond；raulkivi 的渲染服务器用的是 Verovio，也就是我们自己也在用的排版引擎。</p>

<h2 id="musescore-dorico-finale">MuseScore、Dorico 或 Finale 有 MCP 服务器吗？</h2>
<p>有。我们找到了五个 MuseScore 的、两个 Dorico 的和一个 Finale 的，都是 GitHub 上的独立项目，和软件一起运行在你的电脑上。</p>
<div class="table-scroll" role="region" aria-label="能操控 MuseScore、Dorico 或 Finale 的 MCP 服务器" tabindex="0">
<table>
<caption>操控打谱软件（2026年10月3日核查）</caption>
<thead>
<tr><th scope="col">服务器</th><th scope="col">功能</th><th scope="col">运行方式</th><th scope="col">登录</th><th scope="col">写明的客户端</th><th scope="col">最近更新</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://github.com/ghchen99/mcp-musescore">mcp-musescore</a></th><td>操控在 MuseScore 3 或 4 中打开的乐谱：音符、休止符、连音、歌词、小节</td><td>本地，需配合 MuseScore 插件</td><td>无</td><td>Claude Desktop</td><td>2026年10月2日</td></tr>
<tr><th scope="row"><a href="https://github.com/tskovlund/mcp-score">mcp-score</a></th><td>通过 music21 写出 MusicXML；实时编辑 MuseScore Studio 4.4.2 及以上版本中打开的乐谱；渲染文件；Dorico 支持尚属实验性</td><td>本地，需配合 MuseScore 插件</td><td>无</td><td>Claude Code、Claude Desktop、LM Studio</td><td>v0.1.0，2026年9月25日</td></tr>
<tr><th scope="row"><a href="https://github.com/JordanSucher/musescore-mcp">musescore-mcp</a>（JordanSucher）</th><td>在打开的 MuseScore 乐谱中输入基本的音符和休止符</td><td>本地，需配合 MuseScore 插件</td><td>无</td><td>Claude Desktop</td><td>2025年4月20日</td></tr>
<tr><th scope="row"><a href="https://github.com/strongbeen04/MUSESCORE-MCP">musescore-mcp</a>（strongbeen04）</th><td>封装 MuseScore 命令行：格式转换、导出分谱、移调、把 MIDI 转成 MuseScore 文件</td><td>本地</td><td>无</td><td>Claude Desktop、Claude Code</td><td>2026年5月10日</td></tr>
<tr><th scope="row"><a href="https://github.com/achou666666-code/ScoreBridge">ScoreBridge</a></th><td>由 AI 助手识别 PDF 或图片里的印刷乐谱，再转成可编辑的 MuseScore 文件</td><td>本地</td><td>无</td><td>未写明</td><td>v0.1.0，2026年10月1日</td></tr>
<tr><th scope="row"><a href="https://github.com/happycastle114/dorico-mcp-server">dorico-mcp-server</a></th><td>在 Dorico 中输入音符，并提供记谱、和声和配器工具</td><td>本地</td><td>在 Dorico 设置中允许</td><td>Claude Desktop、ChatGPT Desktop</td><td>v0.3.0，2026年1月6日</td></tr>
<tr><th scope="row"><a href="https://github.com/romanstark/dorico-maestro">Dorico Maestro</a></th><td>在 Dorico 4 到 6 中于光标处输入音符、处理 MusicXML 乐谱、检查声部进行、导出 PDF</td><td>本地</td><td>在 Dorico 弹出的提示中确认</td><td>Claude Desktop、Cursor、Antigravity</td><td>v0.1.6，2026年9月10日</td></tr>
<tr><th scope="row"><a href="https://github.com/The-INTJ/FinaleMCP">FinaleMCP</a></th><td>在打开的 Finale 文档中排版和调整格式，每一步都可撤销</td><td>本地，需配合 Lua 桥接</td><td>无</td><td>Claude Code、Claude Desktop</td><td>2026年7月24日</td></tr>
</tbody>
</table>
</div>
<p>它们都通过各软件自己的接口工作：MuseScore 的插件、扩展宿主或命令行；Dorico 的 Remote Control API，需要你在 Dorico 里允许，可以在首选项中设置，也可以在首次连接时弹出的提示中确认；或者 Finale 里的 RGP Lua 脚本。FinaleMCP 的 README 提到，Finale 已于 2024年8月停止开发和销售，但仍能运行。它们都不能导入音频。JordanSucher 的服务器仍出现在各个目录中，但自 2025年4月以来没有更新，所以我们会先试 mcp-musescore 和 mcp-score；mcp-score 自称还处于 alpha 阶段。官方 MCP Registry 中唯一名字带“musescore”的条目 <a href="https://www.npmjs.com/package/@chrischall/musescore-mcp">@chrischall/musescore-mcp</a>，是一个供 Claude 使用的本地包，它通过你已登录的浏览器搜索 musescore.com 网站，而不是编辑乐谱；它最后一次发布是在 2026年9月28日，我们查看时它的 GitHub 仓库返回了 404。</p>

<h2 id="music21-analysis">有用于音乐分析的 music21 MCP 服务器吗？</h2>
<p>有好几个。用 music21 自己文档里的话说，它是“一个基于 Python、用于计算机辅助音乐学研究的工具包”；这些服务器用它或类似的引擎，回答关于 MIDI、MusicXML 或 MEI 文件中调性、和弦、终止式和声部进行的问题。</p>
<div class="table-scroll" role="region" aria-label="用于音乐分析的 MCP 服务器" tabindex="0">
<table>
<caption>音乐分析（2026年10月3日核查）</caption>
<thead>
<tr><th scope="col">服务器</th><th scope="col">功能</th><th scope="col">运行方式</th><th scope="col">登录</th><th scope="col">写明的客户端</th><th scope="col">最近更新</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://github.com/brightlikethelight/music21-mcp-server">music21-mcp-server</a></th><td>13 个工具：调性、罗马数字级数、声部进行、音型模式、配和声、对位</td><td>本地</td><td>无</td><td>Claude Desktop</td><td>v1.0.0，2026年3月18日</td></tr>
<tr><th scope="row"><a href="https://github.com/SimonsonM/music21-mcp">music21-mcp</a>（SimonsonM）</th><td>七个工具：调性、和弦进行、终止式、配和声、对位、MIDI</td><td>本地</td><td>无</td><td>Claude Desktop、Claude Code</td><td>2026年4月18日</td></tr>
<tr><th scope="row"><a href="https://github.com/cclawton/music21-mcp">music21-mcp</a>（cclawton）</th><td>16 个工具，用于读取、编辑和分析 MIDI 文件</td><td>本地</td><td>无</td><td>未写明</td><td>2026年7月14日</td></tr>
<tr><th scope="row"><a href="https://github.com/unimelbmdap/encoding-music-mcp">encoding-music-mcp</a></th><td>分析 MEI 文件（自带 46 份），并用 Verovio 渲染</td><td>本地</td><td>无</td><td>Claude Desktop</td><td>2026年8月31日</td></tr>
<tr><th scope="row"><a href="https://github.com/BluesPrince/thiri-mcp">THIRI Chord Intelligence</a></th><td>和弦分析、和弦排列（voicing）与重配和声；非商业许可</td><td>云端托管或本地</td><td>API 密钥</td><td>Claude、Cursor</td><td>v0.5.3，2026年8月9日</td></tr>
</tbody>
</table>
</div>
<p>星标最多的 music21-mcp-server 需要从源代码安装：我们查看时，它 README 中写的 pip 包在 PyPI 上并不存在，主分支最后一次改动是在 2026年3月19日。除了 MCP 服务器，它还提供一个本地 HTTP API。encoding-music-mcp 出自墨尔本大学的数据分析平台，适合处理学术校订版的 MEI 乐谱。THIRI 回答的是和弦问题，而不是读取整份乐谱。</p>

<h2 id="other-notable">还有哪些值得了解的乐谱 MCP 服务器？</h2>
<p>和扒谱相邻的还有两类工作：把印刷乐谱识别成数字乐谱，以及查找公有领域的乐谱。</p>
<div class="table-scroll" role="region" aria-label="用于识别和查找印刷乐谱的 MCP 服务器" tabindex="0">
<table>
<caption>识别和查找印刷乐谱（2026年10月3日核查）</caption>
<thead>
<tr><th scope="col">服务器</th><th scope="col">功能</th><th scope="col">运行方式</th><th scope="col">登录</th><th scope="col">写明的客户端</th><th scope="col">最近更新</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://melogenai.com/mcp">Melogen</a></th><td>把印刷乐谱（PDF、PNG、JPG）转成 MIDI 和 MusicXML；分析歌曲的速度、调性和结构</td><td>云端托管</td><td>OAuth</td><td>ChatGPT</td><td>未注明日期</td></tr>
<tr><th scope="row"><a href="https://github.com/smeet666/mcp-imslp">mcp-imslp</a></th><td>搜索 IMSLP 上公有领域的作品、版本和作曲家，并给出链接</td><td>本地</td><td>无</td><td>Claude Code、Claude Desktop、Cursor、VS Code</td><td>v1.0.1，2026年8月29日</td></tr>
</tbody>
</table>
</div>
<p>如果想在自己的电脑上识别印刷乐谱，可以用上面 raulkivi 那组服务器里的 omr-mcp，它能把照片或扫描件转成 MusicXML；MuseScore 表格里的 ScoreBridge 则让 AI 助手读谱，再通过 MuseScore 写出来。它们处理的都是页面图像，而不是音频。</p>

<h2 id="how-to-choose">怎么选择乐谱 MCP 服务器？</h2>
<p>先看你要做什么，再看服务器在哪里运行。</p>
<ul>
<li>想在聊天应用里把录音变成可编辑的乐谱，就用托管型的录音转乐谱服务器：ScoreStarling 注册免费账号就能用，Mirelo 则需要 Mirelo 账号。比较一下它们怎么设定速度、一段录音最长能多久，以及会返回哪些文件。</li>
<li>想继续在 MuseScore、Dorico 或 Finale 里工作，就用对应软件的服务器。它们需要软件装在同一台电脑上，通常还要保持打开，并且需要一个能运行本地服务器的客户端。</li>
<li>做分析或教学，可以试试 music21 服务器或 Gradus；处理 MEI 版本，就用 encoding-music-mcp。</li>
<li>想让 AI 助手写谱并展示出来：ABC 可以用 Music Studio 或 Sheet Music Server 示例，LilyPond 可以用 lilypond-mcp。</li>
</ul>
<p>托管型服务器可以添加到 ChatGPT，或网页版和手机上的 Claude，但你的文件会发送给服务提供方，而且大多需要在对方那里注册账号。ChatGPT 的开发者模式支持 OAuth 或无认证连接，而 Claude 的自定义连接器表单还能填写固定的请求头，比如 API 密钥（两者均于 2026年10月3日查阅）。本地服务器把文件留在你的电脑上，需要 Python 或 Node，以及 Claude Desktop、Claude Code、Cursor 或 VS Code 这样的桌面客户端。无论选哪种，都要看看“最近更新”一栏：这里有四个 GitHub 项目，在我们查看前的六个月里，主分支没有任何提交。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://registry.modelcontextprotocol.io/v0/servers?search=musescore">Official MCP Registry API</a>——Model Context Protocol</li>
<li><a href="https://www.pulsemcp.com/servers">PulseMCP server directory</a>——PulseMCP；<a href="https://glama.ai/mcp/servers">MCP servers</a>——Glama；<a href="https://mcp.so/">MCP server directory</a>——mcp.so</li>
<li><a href="https://developers.openai.com/api/docs/guides/developer-mode">ChatGPT Developer mode</a>——OpenAI</li>
<li><a href="https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp">Get started with custom connectors using remote MCP</a>——Claude 帮助中心</li>
<li><a href="https://www.music21.org/music21docs/about/what.html">What is music21?</a>——music21 文档</li>
<li><a href="https://mirelo.ai/mcp">Mirelo MCP</a>、<a href="https://mirelo.ai/blog/introducing-mirelo-mcp">发布文章</a>和<a href="https://github.com/mirelo-ai/mirelo-plugins">插件</a>——Mirelo</li>
<li><a href="https://melogenai.com/mcp">Melogen Music Intelligence for ChatGPT and MCP</a>——Melogen AI</li>
<li><a href="https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server">Example: Sheet Music Server</a>——modelcontextprotocol/ext-apps，GitHub</li>
<li>GitHub 上的 README：<a href="https://github.com/David7ce/audio2score-mcp">David7ce/audio2score-mcp</a>、<a href="https://github.com/psyb0t/docker-audiolla">psyb0t/docker-audiolla</a>、<a href="https://github.com/ALLLLLL703/midi">ALLLLLL703/midi</a>、<a href="https://github.com/blooper20/fingerstyle-tab-mcp">blooper20/fingerstyle-tab-mcp</a>、<a href="https://github.com/linxule/mcp-music-studio">linxule/mcp-music-studio</a>、<a href="https://github.com/delmas41/gradusnotation">delmas41/gradusnotation</a>、<a href="https://github.com/wspringer/lilypond-mcp">wspringer/lilypond-mcp</a>、<a href="https://github.com/raulkivi/sheet-music-mcp">raulkivi/sheet-music-mcp</a>、<a href="https://github.com/ghchen99/mcp-musescore">ghchen99/mcp-musescore</a>、<a href="https://github.com/tskovlund/mcp-score">tskovlund/mcp-score</a>、<a href="https://github.com/JordanSucher/musescore-mcp">JordanSucher/musescore-mcp</a>、<a href="https://github.com/strongbeen04/MUSESCORE-MCP">strongbeen04/MUSESCORE-MCP</a>、<a href="https://github.com/achou666666-code/ScoreBridge">achou666666-code/ScoreBridge</a>、<a href="https://github.com/happycastle114/dorico-mcp-server">happycastle114/dorico-mcp-server</a>、<a href="https://github.com/romanstark/dorico-maestro">romanstark/dorico-maestro</a>、<a href="https://github.com/The-INTJ/FinaleMCP">The-INTJ/FinaleMCP</a>、<a href="https://github.com/brightlikethelight/music21-mcp-server">brightlikethelight/music21-mcp-server</a>、<a href="https://github.com/SimonsonM/music21-mcp">SimonsonM/music21-mcp</a>、<a href="https://github.com/cclawton/music21-mcp">cclawton/music21-mcp</a>、<a href="https://github.com/unimelbmdap/encoding-music-mcp">unimelbmdap/encoding-music-mcp</a>、<a href="https://github.com/BluesPrince/thiri-mcp">BluesPrince/thiri-mcp</a>、<a href="https://github.com/smeet666/mcp-imslp">smeet666/mcp-imslp</a></li>
<li><a href="https://www.npmjs.com/package/@chrischall/musescore-mcp">@chrischall/musescore-mcp</a>——npm</li>
</ol>
</section><h2>常见问题</h2><h3>哪个 MuseScore MCP 服务器最受欢迎？</h3><p>按 2026年10月3日的 GitHub 星标数，ghchen99 的 <a href="https://github.com/ghchen99/mcp-musescore">mcp-musescore</a> 以 105 颗领先，其次是 JordanSucher 的 musescore-mcp（29 颗，自 2025年4月以来没有更新），然后是 tskovlund 的 mcp-score（26 颗）。星标反映的是关注度而不是质量，所以还要看看最后一次提交的时间，以及各自支持哪些 MuseScore 版本。</p><h3>这些 MCP 服务器能在 ChatGPT 里用吗？</h3><p>网页版里只能用托管型的服务器。ChatGPT 的开发者模式向 Plus、Pro、Business、Enterprise 和 Education 账号开放，可以通过 SSE 或流式 HTTP 连接远程服务器，认证方式为 OAuth 或无需认证（2026年10月3日查阅）。本地的 stdio 服务器则需要一个能在你电脑上启动它们的客户端，比如 Claude Desktop、Claude Code、Cursor 或 VS Code。</p><h3>有没有能把音频转成吉他六线谱的 MCP 服务器？</h3><p>fingerstyle-tab-mcp 能在你自己的电脑上用 Demucs 和 Basic Pitch，把吉他录音写成文本格式的吉他六线谱（TAB），并提供 Claude Desktop 的配置步骤。它最后一次提交是在 2026年1月15日。从 2026年10月8日起，ScoreStarling 也能把吉他、贝斯或尤克里里声部显示为六线谱，放在五线谱下方或单独显示，并以文本形式返回 TAB。</p><h3>MuseScore、Steinberg 或 MakeMusic 发布过官方 MCP 服务器吗？</h3><p>在我们 2026年10月3日找到的服务器里没有。这里的 MuseScore、Dorico 和 Finale 服务器都是 GitHub 上的独立项目，Dorico Maestro 的 README 还写明它与 Steinberg 没有关联。</p><h3>使用 MuseScore 的 MCP 服务器，需要先安装 MuseScore 吗？</h3><p>需要。mcp-musescore 要求 MuseScore 3 或 4 正在运行、装好它的插件，并打开一份乐谱。mcp-score 做实时编辑时，需要运行 MuseScore Studio 4.4.2 或更高版本；渲染文件时只需安装 MuseScore，不必运行。</p>]]></content:encoded>
    </item>
    <item>
      <title>MusicXML、MIDI 和 PDF 有什么区别？你该用哪一种</title>
      <link>https://scorestarling.com/zh-hans/blog/musicxml-vs-midi-vs-pdf</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/musicxml-vs-midi-vs-pdf</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>指南</category>
      <description>MusicXML 存的是写出来的乐谱，MIDI 存的是演奏，PDF 存的是印好的页面。本文讲清三种文件各存了什么、该导出哪一种，以及 .mxl 和 .musicxml 有什么不同。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>乐谱还要继续编辑，就导出 MusicXML；音乐要导入 DAW（编曲软件）或合成器，就导出 MIDI；要给人看谱或打印，就导出 PDF。MusicXML 存的是写出来的乐谱：音符的音名写法、调号和拍号、休止符、声部和歌词。MIDI 存的是一次演奏（按了哪个键、什么时候按、按多重），所以要把它变回乐谱只能靠猜。PDF 存的是排好的页面，里面没有任何音乐软件能编辑的内容。</p>
</section>

<h2 id="what-is-musicxml">MusicXML 是什么？</h2>
<p>MusicXML 是一种基于 XML 的开放乐谱文件格式，用于在打谱软件之间交换和存档乐谱。最新的定稿版本 MusicXML 4.0 由 W3C 音乐记谱社区组（Music Notation Community Group）于 2021年6月1日发布，性质是社区组最终报告，和正式的 W3C 标准不是一回事。2026年8月，该社区组表示正在推进 4.1 版。</p>
<p>它按乐谱的写法来描述音乐。音高由音名、升降变化和八度三部分组成，所以 D♯ 和 E♭ 始终是两个不同的音。每个音符既有时值（按四分音符的细分单位计数），也有书面类型，比如附点八分音符。休止符、声部编号、符杠、符干方向、调号（用五度圈上的位置表示：正数代表升号，负数代表降号）、拍号、带段号的歌词，以及可选的页面和谱行排版，都有各自的元素。这种格式还把声音和外观分开：速度可以按每分钟多少个四分音符来存，音符也可以带上细微的起音和释音偏移，这些属于演奏，而不属于谱面。</p>
<p>打谱软件靠它互换乐谱。MuseScore Studio、Dorico 和 Sibelius 的文档都写明支持导入 MusicXML；MakeMusic 的 MusicXML 网站列出了 260 多款支持该格式的应用（2026年10月3日查阅）。</p>

<h2 id="what-is-a-midi-file">MIDI 文件是什么？它缺了什么？</h2>
<p>MIDI 文件是一份按时间排列的演奏事件清单：哪个键在什么时刻、以多大力度按下，又在什么时刻抬起，该用哪种乐器音色发声。MIDI 1.0 诞生于 1983年。它的文件格式“标准 MIDI 文件 1.0”（RP-001，最近一次修订是 1996年2月）就是为了让软件之间能交换带时间戳的 MIDI 数据。</p>
<p>MIDI 文件还能用元事件（meta event）记下几项音乐信息：速度（以每个四分音符多少微秒表示）、拍号、调号（用升号或降号的个数加上大调或小调表示），以及歌词，通常一个音节一个事件。规范要求每个文件写明速度和拍号；没写的话，软件就默认按 4/4 拍、每分钟 120 拍处理。调号和歌词是可选的。</p>
<p>MIDI 说不清的，是音符该怎么写。一个音符只是 0 到 127 之间的一个键号，所以 63 号既是 D♯ 也是 E♭；我们做的第一个转换器把 MIDI 转成乐谱时，每次都把 63 写成 E♭，就连在本该写 D♯ 的 E 大调里也是如此。音的长短只体现为开始和结束时间，所以音符时值只能推断出来。没有休止符（静音只是一段空隙），没有声部、符杠和符干，也没有页面排版。MuseScore Studio 的手册说，MIDI 记录了音符和乐器，“但不含任何视觉格式”。MIDI 里也没有音频：声音来自播放它的合成器。</p>

<h2 id="musicxml-vs-midi-vs-pdf-comparison">MusicXML、MIDI 和 PDF 对比：各自存了什么</h2>
<p>MusicXML 存的是“怎么写”，MIDI 存的是“怎么弹”，PDF 存的是“页面长什么样”。PDF 是通用的页面格式，标准编号为 ISO 32000；PDF 2.0（ISO 32000-2）于 2017年首次发布、2020年修订，美国国会图书馆的格式登记库称它是一种“面向页面”的格式。PDF 乐谱在任何屏幕或打印机上都一模一样，但在音乐软件看来，它只是一张画着音符的图片。</p>
<div class="table-scroll" role="region" aria-label="MusicXML、MIDI 和 PDF 文件各存了什么" tabindex="0">
<table>
<caption>各文件存了什么</caption>
<thead><tr><th scope="col">信息</th><th scope="col">MusicXML</th><th scope="col">MIDI 文件</th><th scope="col">PDF</th></tr></thead>
<tbody>
<tr><th scope="row">音名写法（D♯ 还是 E♭）</th><td>有：音名、升降变化、八度</td><td>无：只有 0 到 127 的键号</td><td>仅印在谱面上</td></tr>
<tr><th scope="row">调号</th><td>有</td><td>可选：升降号个数，以及大调或小调</td><td>仅印在谱面上</td></tr>
<tr><th scope="row">拍号</th><td>有</td><td>建议写入；缺失时默认 4/4 拍</td><td>仅印在谱面上</td></tr>
<tr><th scope="row">音符时值和休止符</th><td>有</td><td>无：只有开始和结束时间</td><td>仅印在谱面上</td></tr>
<tr><th scope="row">声部、符杠、符干</th><td>有</td><td>无</td><td>仅印在谱面上</td></tr>
<tr><th scope="row">歌词</th><td>有，带歌词段号</td><td>可选的歌词事件，通常一个音节一个</td><td>仅印在谱面上</td></tr>
<tr><th scope="row">页面排版</th><td>可选：页面尺寸、页边距、换行和换页</td><td>无</td><td>有，固定不变</td></tr>
<tr><th scope="row">速度</th><td>有，以每分钟四分音符数计</td><td>有，以每个四分音符的微秒数计；缺失时默认 120 BPM</td><td>仅有印在谱面上的速度标记</td></tr>
<tr><th scope="row">精确的时间和演奏力度</th><td>基本没有：只有书面时值，可另加演奏偏移和力度值</td><td>有，每个音符都有</td><td>无</td></tr>
<tr><th scope="row">吉他的弦和品</th><td>有：每个音的弦和品，以及各弦的定弦和变调夹</td><td>无</td><td>仅以六线谱形式印在谱面上</td></tr>
<tr><th scope="row">乐器音色</th><td>乐器名称和可选的 MIDI 音色编号；不含音频</td><td>音色编号；不含音频</td><td>无</td></tr>
<tr><th scope="row">在哪里编辑</th><td>打谱软件</td><td>DAW 和音序器；打谱软件导入时只能靠猜</td><td>无法当作音乐编辑：只能重新录入，或用 OMR 识别</td></tr>
</tbody>
</table>
</div>
<p class="note">“仅印在谱面上”是指人能在页面上读到，但软件没法把它当作音乐来处理。要从 PDF 里取回乐谱，得用光学乐谱识别（OMR）读取图像、推断音符，所以结果需要校对。</p>

<h2 id="which-format-to-export">该导出哪种格式？</h2>
<p>看文件接下来要拿去做什么。</p>
<ul>
<li>有人要编辑乐谱，就发 MusicXML。MuseScore Studio（免费开源）、Dorico 和 Sibelius 都能导入。除非某个工具指明要未压缩的 .musicxml，否则就发 .mxl。</li>
<li>音乐要导入 DAW 或驱动合成器，就发 MIDI。它保留了每个音符的时间和演奏力度，这正是音乐制作需要的。</li>
<li>有人要照着演奏，就发 PDF，打印出来或在平板上看都行；合奏的话，再给每个声部准备分谱。</li>
<li>对方只需要听，就发渲染好的 MP3 或 WAV；MIDI 文件本身不会发出声音。</li>
<li>要存档的话，用 MusicXML 留住音乐内容，用 PDF 留住版面样子。这是我们的习惯，不是硬性规定。</li>
</ul>
<p>Finale 用户还要多考虑一点。MakeMusic 已于 2024年8月26日停止开发和销售 Finale。只要操作系统不出变化，已经装好的 Finale 还能继续用；MakeMusic 对它的技术支持已于 2025年8月结束。MakeMusic 官方的迁移建议是导出 MusicXML（Finale v27 导出的是 MusicXML 4.0），因为 Dorico 无法直接打开 Finale 的 .mus 或 .musx 文件（2026年10月3日查阅）。</p>
<p>想把 MIDI 文件变成 PDF 乐谱，就把它导入打谱软件，在里面整理好，再导出 PDF。MuseScore Studio 能打开 MIDI 文件，还可以在首选项里设定导入时使用的最短音符时值；Dorico Pro 的手册也介绍了它自己的 MIDI 导入和量化选项。不管用哪个，都要做好整理的准备，原因见下文“为什么 MIDI 转乐谱总要靠猜”一节。ScoreStarling 现在也接受 MIDI：点“选择文件”或直接拖入文件，就能排成可编辑的乐谱，并导出 PDF。它会略去鼓轨，只采用第一个拍号，排好后仍需要你过目检查。你也可以导入 MusicXML/MXL、ABC 或印刷乐谱，详见<a href="https://scorestarling.com/zh-hans/how-it-works#sheet-music">目前的输入限制</a>。</p>

<h2 id="mxl-vs-musicxml">MXL 和 MusicXML 有什么区别？</h2>
<p>两者装的是同样的 MusicXML：.mxl 是压缩包，.musicxml 是纯文本文件。W3C 的 MusicXML 4.0 教程推荐的正是这两个扩展名，并指出如果只用 .xml 作扩展名，软件就没法把 MusicXML 和其他 XML 文件区分开来。</p>
<p>.mxl 文件是用 DEFLATE 算法压缩的 zip 包。包里的第一项是一个未压缩的文件，名为 mimetype；位于 META-INF/container.xml 的文件则指明包内乐谱的位置。按教程的说法，压缩后的 MusicXML 大约和等价的 MIDI 文件一样大，所以我们会发 .mxl。MuseScore Studio 默认导出的就是压缩的 MusicXML。如果你想自己阅读、比对或处理 XML，就选 .musicxml。两者的媒体类型也不同：.mxl 是 <code>application/vnd.recordare.musicxml</code>，.musicxml 是 <code>application/vnd.recordare.musicxml+xml</code>。</p>

<h2 id="midi-to-sheet-music-guesswork">为什么 MIDI 转乐谱总要靠猜？</h2>
<p>MIDI 文件从来不说明音符该怎么写，所以任何把 MIDI 转成乐谱的软件，都得自己决定调性、音名写法、音符时值、声部，以及休止符放在哪里。</p>
<p>我们实测过它会怎么出错。2026年10月2日，我们从公开数据集 GuitarSet 中取了 12 段吉他录音，用 Basic Pitch 转出 MIDI，交给我们最早基于 music21 库写的转换器。结果 12 份乐谱都没有调号；所有休止符都被隐藏（1,626 个中可见的为 0）；一行谱表上最多叠了五个声部；在 2,703 个调内音中，有 322 个的写法与调性相悖。</p>
<p>换用 MuseScore Studio 4.7.5 的 MIDI 导入器后，这些问题大多解决了，前提是先把 MIDI 处理好：把音符对齐到十六分音符网格，再根据音符估出调性，写进每一条有音符的音轨。少了这一步，MuseScore 会把没对齐网格的 MIDI 当成现场演奏，重新猜一遍拍号、速度和调。我们还把导入限制为每行谱表最多两个声部，并关闭了连音识别。在这些设置下，12 份里有 10 份的调号与数据集标注一致（另外两份都差一个五度），3,213 个音符里写法与调性相悖的为 0，988 个休止符全部可见，没有哪行谱表超过两个声部。完整的前后对比见《<a href="https://scorestarling.com/zh-hans/blog/why-midi-imports-look-messy">MIDI 导入 MuseScore 为什么乱糟糟</a>》。</p>

<h2 id="musicxml-import-differences">为什么 MusicXML 换个软件打开就变样？</h2>
<p>MusicXML 能把音乐内容完整地带过去，但每个软件都有自己的字体、间距和默认设置，所以导入后版面和小细节常会变样。MuseScore Studio 的手册说，音符和乐器编制都能如实导入，但要让导入的乐谱看起来和原版一样，通常还得整理一番。它的导入选项可以保留文件里的页面尺寸、页边距、谱表大小和换行换页，不过由于间距和字体不同，换行换页的位置未必和原来一致。MakeMusic 关于从 Finale 迁移到 Dorico 的指南也是这个意思：音乐内容，以及页面和谱表尺寸这类基本版面，都能顺利转过去，而对单个元素做的图形微调通常带不过去。</p>
<p>我们在 2026年10月2日自己做过一次往返测试，规模很小：MuseScore Studio 4.7.5 的命令行转换器重新导入了我们一份只有三个音符的 MusicXML 样例，音高、位置和时值完全一致，但它把我们的记谱人署名挪到了作曲者的位置，还丢掉了“记谱人”这个身份。Sibelius 和 Dorico 我们没有测试。把乐谱转给别人之前，先在对方要用的软件里打开，检查署名、版面，以及你手动调整过的地方。</p>

<h2 id="what-scorestarling-exports">ScoreStarling 导出哪些文件，为什么 MIDI 和 MusicXML 不一样？</h2>
<p>两种文件 ScoreStarling 都能导出，而且两者在时间上故意不一致：MIDI 保留演奏，MusicXML 保留一份写给人读的版本。</p>
<p>在我们 10月2日针对“单件乐器”选项的测试中，转谱先由 Basic Pitch 按给定的速度生成 MIDI。MuseScore Studio 再根据一份对齐到十六分音符网格的副本写出乐谱：4/4 拍，调性由音符估算，休止符可见，每行谱表最多两个声部。它还会简化时值：在上文那 12 段吉他录音中，它把 26% 的音符写得比对齐后的演奏更长，最多长出三个十六分音符，从来没有写短过。演奏者想看到一个干净的附点四分音符，制作人想要的是原汁原味的律动。所以播放和 MIDI 导出保留你的演奏时间，而 MusicXML、PDF 和页面图片呈现的是书面版本。改音高会同时改动两种文件；你设定某个音符的书面时值时，它对应的 MIDI 音符会以原本弹奏的位置为起点调整长度。别的工具也是这样划分的：Klangio 的 Sing2Notes 应用同时提供量化和未量化两种 MIDI 下载（2026年10月3日查阅）。</p>
<p>ScoreStarling 免费提供的下载，以及解锁后才有的下载：</p>
<div class="table-scroll" role="region" aria-label="ScoreStarling 的免费下载和解锁后下载" tabindex="0">
<table>
<caption>ScoreStarling 可下载的文件（2026年10月8日）</caption>
<thead><tr><th scope="col">获取方式</th><th scope="col">文件</th></tr></thead>
<tbody>
<tr><th scope="row">免费</th><td>PDF、页面图片、单独的 PDF 分谱、简谱 PDF（全部谱表或仅旋律）、带吉他六线谱的 PDF、TAB 文本、MP3 和 WAV。乐谱解锁前，每页和 TAB 文本底部都有一行页脚小字：“Made with ScoreStarling · scorestarling.com”。</td></tr>
<tr><th scope="row">解锁后（每份乐谱一次性 15 积分）</th><td>MusicXML（.musicxml 和 .mxl；显示为 TAB 谱的声部会带上 TAB 谱表）、MIDI、ABC 和 MEI，页面也不再带页脚小字。用积分转出的乐队乐谱本身就已解锁；订阅 Plus 期间，所有乐谱都会解锁。</td></tr>
</tbody>
</table>
</div>
<p>工作台的“下载”菜单提供的 MusicXML 是 .musicxml 格式；.mxl 和 MEI 要通过 AI 助手获取。单件乐器或人声、钢琴独奏的转谱（合计每天最多 3 次）以及导入和编辑都免费；乐队需要积分。简谱在工作台和对话面板里也能以只读视图查看，编辑请在五线谱中进行。<a href="https://scorestarling.com/zh-hans/how-it-works#notation">简谱在保留记谱信息方面有局限</a>。MP3 和 WAV 是按 General MIDI 音色渲染的乐谱，不是从你的录音里分离出的分轨。详情见<a href="https://scorestarling.com/zh-hans/how-it-works#specs">技术规格</a>。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://www.w3.org/2021/06/musicxml40/">MusicXML 4.0</a> 和 <a href="https://www.w3.org/community/music-notation/">Music Notation Community Group</a>——W3C</li>
<li><a href="https://www.w3.org/2021/06/musicxml40/tutorial/notation-basics/">Notation basics in MusicXML</a> 和 <a href="https://www.w3.org/2021/06/musicxml40/tutorial/midi-compatible-part/">A MIDI-compatible part</a>，MusicXML 4.0 教程——W3C Music Notation Community Group</li>
<li><a href="https://www.w3.org/2021/06/musicxml40/tutorial/compressed-mxl-files/">Compressed .mxl files</a>，MusicXML 4.0 教程——W3C Music Notation Community Group</li>
<li>MusicXML 4.0 参考文档：<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/note/">note</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/fifths/">fifths</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/lyric/">lyric</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/print/">print</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/midi-instrument/">midi-instrument</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/string/">string</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/fret/">fret</a>、<a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/staff-tuning/">staff-tuning</a> 和 <a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/capo/">capo</a> 元素——W3C Music Notation Community Group</li>
<li><a href="https://midi.org/midi-1-0">MIDI 1.0</a>——The MIDI Association</li>
<li><a href="https://midi.org/summary-of-midi-1-0-messages">Summary of MIDI 1.0 messages</a>——The MIDI Association</li>
<li><a href="https://midi.org/standard-midi-files">Standard MIDI Files</a> 和 <a href="https://midi.org/standard-midi-files-specification">Standard MIDI Files 1.0 specification (RP-001)</a>——The MIDI Association</li>
<li><a href="https://www.loc.gov/preservation/digital/formats/fdd/fdd000474.shtml">PDF 2.0, ISO 32000-2</a>——美国国会图书馆，Sustainability of Digital Formats</li>
<li><a href="https://handbook.musescore.org/file-management/file-export">File export</a>、<a href="https://handbook.musescore.org/file-management/working-with-musicxml-files">Working with MusicXML files</a>、<a href="https://handbook.musescore.org/file-management/opening-and-saving-scores">Opening and saving scores</a> 和 <a href="https://handbook.musescore.org/customization/preferences">Preferences</a>——MuseScore Studio 手册</li>
<li><a href="https://www.steinberg.help/r/dorico-pro/6.2/en/dorico/topics/project_file_handling/project_file_handling_musicxml_files_importing_t.html">Importing MusicXML files</a> 和 <a href="https://www.steinberg.help/r/dorico-pro/6.2/en/dorico/topics/project_file_handling/project_file_handling_midi_importing_t.html">Importing MIDI</a>，Dorico Pro 6.2 手册——Steinberg</li>
<li><a href="https://resources.avid.com/SupportFiles/Sibelius/Using_Sibelius_for_mobile_EN.pdf">Using Sibelius for Mobile on iPhone, iPad, and Android</a>——Avid</li>
<li><a href="https://www.finalemusic.com/blog/end-of-finale-new-journey-dorico-letter-from-president/">The End of Finale</a> 和 <a href="https://www.finalemusic.com/blog/from-finale-to-dorico-a-migration-guide/">From Finale to Dorico, a migration guide</a>——MakeMusic</li>
<li><a href="https://www.musicxml.com/">MusicXML for exchanging digital sheet music</a>——MakeMusic</li>
<li><a href="https://klang.io/sing2notes/">Sing2Notes</a>——Klangio</li>
</ol>
</section><h2>常见问题</h2><h3>MIDI 文件里有声音吗？</h3><p>没有。它存的只是指令：弹哪个键、什么时候弹、弹多重，外加一个乐器音色编号。声音来自播放它的合成器或采样音源，所以同一个 MIDI 文件在不同软件里听起来不一样。</p><h3>为什么我的 MIDI 文件一打开就是 4/4 拍、120 BPM？</h3><p>标准 MIDI 文件规范要求每个文件写明速度和拍号，并规定文件没写时，软件一律按 4/4 拍、每分钟 120 拍处理。先把真实的速度和拍号设好，再判断乐谱写得对不对。</p><h3>PDF 能转回 MusicXML 吗？</h3><p>只能借助光学乐谱识别（OMR）：它读取页面图像，推断出谱面内容，所以结果需要校对。如果原始的打谱文件或它的 MusicXML 还在，就直接从那里开始。</p><h3>MusicXML 和 MuseScore、Sibelius 的文件是一回事吗？</h3><p>不是。MuseScore Studio 的 .mscz 和 Sibelius 的 .sib 都是各自软件的自有格式，MusicXML 则是软件之间交换乐谱用的格式。比如 MuseScore Studio 除了自己的格式，也能打开 MusicXML、压缩的 MusicXML 和 MIDI 文件。</p><h3>给合唱团或乐队，该发哪种文件？</h3><p>看谱用 PDF；如果每位乐手只需要自己那一行，就发分谱。如果有人要移调、改编或修改分谱，再附上 MusicXML。</p>]]></content:encoded>
    </item>
    <item>
      <title>远程 MCP 服务器的 OAuth 实战：上线经验总结</title>
      <link>https://scorestarling.com/zh-hans/blog/remote-mcp-server-oauth</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/remote-mcp-server-oauth</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>技术</category>
      <description>我们托管的 MCP 服务器怎样借助 OAuth 2.1 和 Supabase Auth 让用户登录：401 质询、RFC 9728 元数据、客户端注册、令牌受众和令牌刷新。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>远程 MCP 服务器是一个 OAuth 受保护资源。请求不带令牌时，它返回 401，并指向自己的 RFC 9728 元数据；客户端顺着元数据找到授权服务器，完成注册，用 PKCE 和 <code>resource</code> 参数让用户登录，最后带着为该服务器 URL 签发的 Bearer 令牌回来。我们在生产环境中用 Supabase Auth 跑这套流程。真正费工夫的是三个细节：401 要带 scope 提示而不带错误码；用令牌钩子把我们的受众和 scope 写进 Supabase 签发的令牌；还有动态客户端注册，因为我们的授权服务器不支持客户端 ID 元数据文档。</p>
</section>

<h2 id="mcp-oauth-roles">MCP 服务器接入 OAuth 时，各方分别负责什么？</h2>
<p>MCP 服务器的活最少：只负责接受或拒绝 Bearer 令牌。MCP 授权规范（2025-11-25 版）把它定为 OAuth 2.1 资源服务器，把 AI 助手定为 OAuth 客户端，并允许负责用户登录和签发令牌的授权服务器是一个独立的服务。</p>
<p>我们的授权服务器就是独立的。所有令牌都由 Supabase Auth 的 OAuth 2.1 服务器签发，登录和授权同意页面由我们的网站提供，<code>https://mcp.scorestarling.com/mcp</code> 这个端点只负责校验令牌。它以 Streamable HTTP 方式运行，不使用 MCP 会话，每个请求都会校验令牌。ScoreStarling 能把录音变成可编辑的乐谱，AI 助手要调用它，走的就是这个端点；用户要做的操作见<a href="https://scorestarling.com/zh-hans/mcp">连接指南</a>。</p>

<h2 id="mcp-oauth-flow">MCP 客户端连接时会发生什么？</h2>
<p>客户端一开始只有一个 URL。我们服务器的两次响应（以下为 2026年10月3日的实际内容）告诉它去哪里登录，之后再经过六步拿到令牌。首先，不带令牌的请求会收到 401，其 <code>WWW-Authenticate</code> 响应头写明了元数据的位置和要申请的 scope：</p>
<pre><code>$ curl -si -X POST https://mcp.scorestarling.com/mcp \
    -H 'content-type: application/json' \
    -H 'accept: application/json, text/event-stream' -d '{}'
HTTP/2 401
content-type: application/json
www-authenticate: Bearer resource_metadata="https://mcp.scorestarling.com/.well-known/oauth-protected-resource/mcp", scope="openid email profile"

{"error_description": "Authentication required"}</code></pre>
<p>然后，客户端获取这份元数据。按照 RFC 9728，元数据地址是在主机名和路径之间插入 <code>/.well-known/oauth-protected-resource</code> 得到的；它还要求 <code>resource</code> 与客户端正在使用的 URL 完全一致，否则客户端必须停止。下面我们把 Supabase 项目 ID 换成了占位符：</p>
<pre><code>{
  "resource": "https://mcp.scorestarling.com/mcp",
  "authorization_servers": ["https://&lt;project-ref&gt;.supabase.co/auth/v1"],
  "scopes_supported": ["openid", "email", "profile"],
  "bearer_methods_supported": ["header"]
}</code></pre>
<p>其余六步：</p>
<ol>
<li><p>客户端读取授权服务器的元数据。Supabase 的 issuer 带路径，所以按照 RFC 8414，well-known 段要放在路径前面：<code>https://&lt;project-ref&gt;.supabase.co/.well-known/oauth-authorization-server/auth/v1</code>。客户端会检查 <code>issuer</code> 是否完全一致，以及 <code>code_challenge_methods_supported</code> 里是否有 <code>S256</code>；MCP 规范规定，缺少这个字段就必须停止。</p></li>
<li><p>客户端把自己的元数据 POST 到元数据中公布的 <code>registration_endpoint</code>（动态客户端注册，RFC 7591），拿到一个 <code>client_id</code>。我们的测试客户端以公共客户端身份注册，不带密钥；Anthropic 的文档说 Claude 也是这样。</p></li>
<li><p>客户端在浏览器中打开授权端点，带上 PKCE S256 challenge、401 里给出的 scope，以及 <code>resource=https://mcp.scorestarling.com/mcp</code>（RFC 8707）。Supabase 把浏览器转交给我们的授权同意页面：用户需要时先登录，再选择批准或拒绝这个客户端。</p></li>
<li><p>客户端用 PKCE verifier 和同一个 <code>resource</code> 兑换授权码，拿到一个有效期一小时的访问令牌和一个刷新令牌。</p></li>
<li><p>之后的每个 MCP 请求都带上 <code>Authorization: Bearer …</code>。我们先用 issuer 公布的公钥校验签名，再检查 issuer、受众、过期时间和写入 scope，最后确认账号没有被停用。</p></li>
<li><p>令牌过期，或者我们返回带 <code>invalid_token</code> 的 401 时，客户端会刷新令牌（同样带上 <code>resource</code>），拿到新的访问令牌和刷新令牌。</p></li>
</ol>
<p>即使授权服务器会忽略 <code>resource</code>，客户端也必须在授权请求和令牌请求中带上它。Supabase 的 OAuth 流程文档没有提到这个参数（2026年10月3日查看）。在我们所有的线上实测中，Supabase 都接受了它，而我们令牌里的受众来自钩子。</p>

<h2 id="mcp-401-www-authenticate">401 响应的 WWW-Authenticate 头应该包含什么？</h2>
<p>请求没带令牌时：写明元数据在哪里、该申请哪些 scope，不带错误码。RFC 6750 §3.1 指出，完全不带认证信息的请求不应返回错误码。<code>invalid_token</code> 用于带了令牌但校验失败的情况，告诉客户端可以换个新令牌再重试。MCP 规范还补充说，401 应带上 <code>scope</code> 提示，客户端必须把它当作要申请的 scope。</p>
<p>我们是在写连接指南时，发现自己的服务器在这一点上做错了。在 MCP Python SDK 1.30.0 中，<code>RequireAuthMiddleware</code> 对所有未认证的请求都返回 <code>error="invalid_token"</code>，而且不带 scope，结果一个从没发过令牌的客户端被告知“令牌无效”。现在我们在 MCP 路由外面包了一层小小的 ASGI 中间件，只改写 SDK 返回的 401；放不放行仍由 SDK 决定，它返回的 403 <code>insufficient_scope</code> 也原样保留：</p>
<pre><code># No token, before the fix (the SDK's default; wrapped for reading)
WWW-Authenticate: Bearer error="invalid_token",
    error_description="Authentication required",
    resource_metadata="…/.well-known/oauth-protected-resource/mcp"

# No token, now
WWW-Authenticate: Bearer
    resource_metadata="…/.well-known/oauth-protected-resource/mcp",
    scope="openid email profile"

# Rejected token: expired, wrong audience, malformed or account turned off
WWW-Authenticate: Bearer error="invalid_token",
    error_description="Invalid or expired access token",
    resource_metadata="…/.well-known/oauth-protected-resource/mcp",
    scope="openid email profile"</code></pre>
<p>提示里写的是 <code>openid email profile</code>，也就是 Supabase 能授予的 scope，与 <code>scopes_supported</code> 一致。现在只要无令牌时的质询里带了错误码，我们的部署检查就会失败。这个修复在 2026年10月2日上线，距离合并不到四分钟。Anthropic 的连接器文档还为 Claude 补充了两条规则：登录只会由 401 触发（Claude 会忽略 200 响应里的 <code>WWW-Authenticate</code>），而且只使用 <code>authorization_servers</code> 中的第一项。</p>

<h2 id="supabase-custom-access-token-hook">怎样把 MCP 受众和 scope 写进 Supabase 的令牌？</h2>
<p>用自定义访问令牌钩子。MCP 规范要求服务器只接受为自己签发的令牌，实际上就是要检查 <code>aud</code>；我们还想要一个自己的写入 scope，而 Supabase 的文档说它的 OAuth 服务器不支持自定义 scope（2026年10月3日查看）。这个钩子是一个 Postgres 函数，Supabase Auth 在签发令牌前运行它，它可以改写令牌中的 claims。下面是我们的版本，在迁移文件的基础上略有简化：</p>
<pre><code>create or replace function scorestarling.access_token_hook(event jsonb)
returns jsonb language plpgsql stable security invoker set search_path = '' as $$
declare claims jsonb := event-&gt;'claims';
begin
  -- Tokens issued to an OAuth client carry its client_id; website sessions don't.
  if coalesce(claims-&gt;&gt;'client_id', '') &lt;&gt; '' then
    claims := jsonb_set(claims, '{aud}', '"https://mcp.scorestarling.com/mcp"');
    claims := jsonb_set(claims, '{scope}', '"openid email profile scorestarling:write"');
  end if;
  return jsonb_build_object('claims', claims);
end;
$$;
grant usage on schema scorestarling to supabase_auth_admin;
grant execute on function scorestarling.access_token_hook(jsonb) to supabase_auth_admin;
revoke all on function scorestarling.access_token_hook(jsonb) from public, anon, authenticated;</code></pre>
<p>MCP 服务端用 PyJWT 校验这些 claims，然后再执行两条自己的规则：</p>
<pre><code>claims = jwt.decode(token, key, algorithms=["RS256", "ES256"],
                    issuer=ISSUER, audience=ACCEPTED_AUDIENCES,
                    options={"require": ["exp", "iat", "iss", "aud", "sub"]})
if "scorestarling:write" not in claims.get("scope", "").split():
    return None  # the client gets 401 invalid_token
# then: has this account been turned off?</code></pre>
<p><code>ACCEPTED_AUDIENCES</code> 是 MCP 的 URL，迁移期间还会加上旧 URL。对 client_id 的判断把网站自己的登录挡在了外面：同一个 Supabase 项目签发的浏览器会话令牌，issuer 和密钥都一样，但没有 <code>client_id</code>，所以永远拿不到 MCP 受众。</p>
<p>这个钩子有两个代价。第一，它写入的是一个固定的受众，而不是跟随客户端传来的 <code>resource</code>；只要一个授权服务器只服务一个 MCP 服务器，这就没问题。第二，我们的授权同意页面只能显示客户端申请的 scope，所以写入 scope 实际上是在用户批准客户端时授予的。</p>
<p class="note">有一个尚未关闭的问题报告 <a href="https://github.com/supabase/auth/issues/2820">supabase/auth#2820</a>（2026年9月20日提交，截至 10月3日无人回复）称，对于公共客户端、<code>offline_access</code> 或 <code>resource</code>，Supabase 获取授权详情的请求会返回 400。这几种情况我们都没能复现；在我们读过的 Supabase Auth 源代码里，只有授权已不处于待处理状态时（比如同一个授权被获取了两次），才会出现这个 400。</p>

<h2 id="dcr-vs-cimd">动态客户端注册，还是客户端 ID 元数据文档？</h2>
<p>授权服务器声明支持哪种就用哪种；目前用 Supabase 的话，就是动态客户端注册（DCR）。2025-11-25 版 MCP 规范建议，在客户端和服务器素不相识时使用客户端 ID 元数据文档（CIMD），保留 DCR 是为了向后兼容。客户端应先尝试预先分配的客户端 ID，然后是 CIMD，再然后是 DCR，都不行才去问用户。</p>
<div class="table-scroll" role="region" aria-label="MCP 客户端获得客户端 ID 的几种方式" tabindex="0">
<table>
<caption>MCP 客户端如何获得客户端 ID</caption>
<thead><tr><th scope="col">方式</th><th scope="col">客户端如何表明身份</th><th scope="col">服务器元数据中的标志</th><th scope="col">Claude 表单中的选项</th><th scope="col">我们的服务器（2026年10月3日）</th></tr></thead>
<tbody>
<tr><th scope="row">预注册</th><td>预先创建的客户端 ID，手动填写或内置在客户端中</td><td>无需</td><td>Use your own OAuth client（使用自己的 OAuth 客户端）</td><td>不提供</td></tr>
<tr><th scope="row">客户端 ID 元数据文档</th><td><code>client_id</code> 是一个 HTTPS URL，指向客户端托管的 JSON 文件，由服务器去获取</td><td><code>client_id_metadata_document_supported</code></td><td>Use Claude’s published identity（推荐，使用 Claude 已发布的身份）</td><td>未声明支持</td></tr>
<tr><th scope="row">动态客户端注册</th><td>把自己的元数据 POST 到注册端点，拿到 <code>client_id</code></td><td><code>registration_endpoint</code></td><td>Register automatically（自动注册）</td><td>已声明支持</td></tr>
</tbody>
</table>
</div>
<p>我们在 2026年10月3日重新读取了 Supabase 的元数据：其中列出了注册端点、S256 和令牌端点认证方式 <code>none</code>，没有 CIMD 标志；它的 MCP 指南会让你在控制台里开启动态注册。</p>
<p>这决定了用户在 Claude 自定义连接器表单里该怎么选。Anthropic 的文档把推荐选项，也就是 Claude 已发布的身份（published identity），描述为由 Anthropic 托管、需要服务器支持的 CIMD。所以我们的指南要求选择“Sign in now”（我们的工具没有一个能在不登录时使用）和“Register automatically”。Anthropic 的开发者文档说，服务器没有声明支持 CIMD 时，Claude 会回退到 DCR（CIMD 还要求 <code>token_endpoint_auth_methods_supported</code> 中包含 <code>none</code>）；我们没有测试过保留表单默认值的情况，所以要求明确选择。我们在 2026年10月2日读过的客户端文档显示，ChatGPT 开发者模式、Cursor、VS Code 和 Gemini CLI 都使用 DCR。</p>
<p>DCR 也有代价：Anthropic 提到，Claude 每次全新连接都会注册一个新客户端；Supabase 也提醒，开启动态注册后，任何 MCP 客户端都能在你的项目中注册。不过，注册本身不授予任何权限。用户仍然要登录并批准这个客户端，令牌校验逻辑也会在每个请求上检查账号。由于任何客户端都能以任意名称注册，从 2026年10月6日起，我们的授权同意页面会显示批准后用户将被带往哪里；如果某个应用自称 ChatGPT 或 Claude，回调地址却不是对应助手的地址，页面就会发出警告。</p>

<h2 id="mcp-refresh-tokens">访问令牌过期后，MCP 客户端还能保持登录吗？</h2>
<p>能。访问令牌的有效期是一小时，客户端用刷新令牌续期。在依赖这一点之前，我们读了 Supabase Auth 的源代码（<code>master</code> 分支，截至 2026年9月22日）：</p>
<ul>
<li>每次授权码兑换都会返回刷新令牌，无论是否申请了 <code>offline_access</code>；</li>
<li>刷新必须由该会话所属的客户端发起，刷新时会轮换刷新令牌，并保留 <code>client_id</code> claim；</li>
<li>刷新时钩子同样会运行（此时 <code>authentication_method</code> 为 <code>token_refresh</code>），所以每次都会重新写入我们的受众和 scope。</li>
</ul>
<p>2026年10月2日的线上实测与此一致：刷新令牌发生了轮换，续期后的访问令牌可以正常使用。Anthropic 的文档说，Claude 会在收到 401 后以及令牌到期前五分钟内刷新令牌；如果授权服务器在 <code>scopes_supported</code> 中列出了 <code>offline_access</code>，Claude 还会申请它。Supabase 的元数据列出了它，我们额外申请 <code>offline_access</code> 的检查也通过了。</p>
<p>以下情况仍会结束会话：</p>
<ul>
<li>账号被停用，或用户被删除、被封禁（在我们的线上实测中，停用账号后，浏览器会话和两个 MCP 令牌都被拒绝了）；</li>
<li>旧的刷新令牌在 Supabase 的重用间隔（默认 10 秒）之后再次被使用，这会吊销整个会话；</li>
<li>开启了会话时长上限、闲置超时或“single session per user”（每个用户只保留一个会话）。最后这项一定要关掉，否则在网站上登录一次，助手的会话就会结束；</li>
<li>用户用 Supabase 默认的 <code>logout</code> 退出网站登录，这会结束该账号的所有会话，包括助手的会话。我们的网站在 2026年10月6日之前就是这样，这很可能就是某个 ChatGPT 连接无法再续期令牌的原因；现在“退出登录”会发送 <code>scope=local</code>，只结束当前浏览器的会话。</li>
</ul>
<p>Supabase 没有公布令牌吊销端点（2026年10月3日查看），所以我们切断访问的办法就是停用账号。</p>

<h2 id="oauth-issuer-trailing-slash">issuer 末尾多一个斜杠，为什么会出问题？</h2>
<p>因为 issuer 是逐字符比较的。RFC 8414 要求元数据中的 <code>issuer</code> 与客户端用来查找它的那个值完全相同，RFC 9728 对 <code>resource</code> 也有同样的要求。对严格的客户端来说，<code>https://auth.example.com</code> 和 <code>https://auth.example.com/</code> 是两个不同的 issuer。</p>
<p>MCP Python SDK 用 pydantic 的 <code>AnyHttpUrl</code> 存储 issuer，而我们 lockfile 里锁定的 pydantic 2.13.5 会把 <code>https://auth.example.com</code> 变成 <code>https://auth.example.com/</code>。用它生成的元数据，写的是一个授权服务器从未公布过的 issuer，所以我们的服务器自己生成受保护资源元数据，issuer 与配置的值完全一致。Supabase 的 issuer 以路径结尾，pydantic 不会改动它，所以我们在生产环境中从没碰到这个问题；但如果 issuer 只是一个不带路径的源（origin），就会中招。现在，只要授权服务器的 <code>issuer</code> 和我们的不完全一致，部署检查就会失败。</p>

<h2 id="verified-and-not-verified">哪些已经验证，哪些还没有</h2>
<p>最初的证据来自一个按 MCP 规范编写的脚本化客户端。2026年10月2日，我们对生产环境跑了一轮测试，覆盖了无令牌时的 401、公共客户端的动态注册、带 <code>resource</code> 的授权同意和授权码兑换、带轮换的令牌刷新、用两个令牌分别调用 <code>tools/list</code>，以及停用账号（停用后两个令牌和浏览器会话都被拒绝）。第二轮额外申请了 <code>offline_access</code>，两轮都通过了。那次测试用的还是我们之前的地址；迁移到 <code>mcp.scorestarling.com</code> 的过程<a href="https://scorestarling.com/zh-hans/blog/mcp-server-url">另有一篇文章</a>。</p>
<p>此后，真实客户端也连上了。2026年10月3日，ChatGPT 开发者模式下的应用连上 <code>mcp.scorestarling.com</code>，完成登录并调用了一个工具；10月6日，Claude 也成功连接并运行了工具。尚未验证的有：</p>
<ul>
<li>令牌真正过期后的刷新：脚本里的刷新是在登录后立即执行的，我们还没有追踪过真实客户端的刷新；</li>
<li>Supabase 托管端的会话设置，我们还没有回读核对。</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization">MCP 规范（2025-11-25 版）：授权</a> — Model Context Protocol</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc9728">RFC 9728：OAuth 2.0 受保护资源元数据</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc6750#section-3.1">RFC 6750：OAuth 2.0 授权框架：Bearer 令牌的使用，§3.1</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc8707">RFC 8707：OAuth 2.0 资源指示符</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc7591">RFC 7591：OAuth 2.0 动态客户端注册协议</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc8414">RFC 8414：OAuth 2.0 授权服务器元数据</a> — IETF</li>
<li><a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/">OAuth 客户端 ID 元数据文档（互联网草案）</a> — IETF OAuth 工作组</li>
<li><a href="https://claude.com/docs/connectors/building/authentication">连接器的身份验证</a> — Anthropic</li>
<li><a href="https://claude.com/docs/connectors/custom/remote-mcp">添加不在目录中的连接器</a> — Anthropic</li>
<li><a href="https://supabase.com/docs/guides/auth/oauth-server/oauth-flows">OAuth 2.1 流程</a> — Supabase</li>
<li><a href="https://supabase.com/docs/guides/auth/auth-hooks/custom-access-token-hook">自定义访问令牌钩子</a> — Supabase</li>
<li><a href="https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication">Model Context Protocol（MCP）身份验证</a> — Supabase</li>
<li><a href="https://supabase.com/docs/guides/auth/sessions">用户会话</a> — Supabase</li>
<li><a href="https://github.com/supabase/auth/issues/2820">supabase/auth 的 issue #2820</a> — GitHub</li>
</ol>
</section><h2>常见问题</h2><h3>远程 MCP 服务器需要自己的授权服务器吗？</h3><p>不需要。MCP 授权规范允许授权服务器是一个独立的服务。MCP 服务器发布 RFC 9728 元数据来指明授权服务器，再校验它签发的令牌，包括令牌的受众。我们的服务器指向 Supabase Auth，自己只负责校验令牌。</p><h3>请求没带令牌时，MCP 服务器的 401 该带 invalid_token 错误吗？</h3><p>不该。RFC 6750 §3.1 指出，不带凭据的请求不应返回错误码。应该返回 <code>resource_metadata</code> 和 <code>scope</code> 提示，把 <code>invalid_token</code> 留给带了令牌却被拒绝的情况，用来通知客户端刷新令牌。</p><h3>Supabase Auth 能为 MCP 服务器签发自定义 scope 吗？</h3><p>截至 2026年10月3日还不能：Supabase 的 OAuth 2.1 文档写明不支持自定义 scope。我们请求的是 <code>openid email profile</code>，再通过自定义访问令牌钩子（custom access token hook）加上我们自己的 scope 和 MCP 受众，每次刷新令牌时这个钩子也会运行。</p><h3>服务器不支持 CIMD 时，在 Claude 的自定义连接器表单里该怎么选？</h3><p>在“OAuth client”下选“Register automatically”，也就是动态客户端注册；如果所有工具都要求账号，再在“Authentication”下选“Sign in now”。Claude 推荐的选项“Use Claude’s published identity”是一份客户端 ID 元数据文档，需要服务器支持。我们的 <a href="https://scorestarling.com/zh-hans/mcp#claude">Claude 连接步骤</a>对这两项选择都有说明。</p><h3>要从 Supabase 拿到刷新令牌，必须申请 offline_access scope 吗？</h3><p>在我们的测试中不需要。不申请它，Supabase 在每次授权码兑换时也都返回了刷新令牌：从源代码看是这样，2026年10月2日的线上实测也是这样。有些客户端还是会申请它；Anthropic 的文档说，授权服务器列出了它时 Claude 就会申请，我们带上它的检查也通过了。</p>]]></content:encoded>
    </item>
    <item>
      <title>MCP 服务器该放在什么地址？我们查了 33 个</title>
      <link>https://scorestarling.com/zh-hans/blog/mcp-server-url</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/mcp-server-url</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>技术</category>
      <description>2026年10月2日，我们探测了 33 个公开 MCP 端点，要求登录的 22 个服务中有 14 个用 mcp.&lt;domain&gt;/mcp。另附我们选定地址并完成迁移的过程。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>我们查过的远程 MCP 服务器，大多有自己独立的域名。2026年10月2日，在端点要求登录的 22 个服务中，14 个用的是 <code>mcp.&lt;domain&gt;/mcp</code>，另有 6 个用 <code>mcp.</code> 域名但不带路径或带版本号路径，还有 2 个用 <code>api.</code> 域名。地址没有统一标准，路径也猜不出来，所以要公布确切的 URL。我们把自己的地址从 <code>scorestarling.com/mcp</code> 迁到了 <code>https://mcp.scorestarling.com/mcp</code>，好让网站上的 <code>/mcp</code> 变成给人看的连接指南。</p>
</section>

<h2 id="mcp-server-url-survey">公开的 MCP 服务器都用什么 URL？</h2>
<p>大多是专用的 <code>mcp.</code> 子域名，路径通常是 <code>/mcp</code>。当时我们正在 <code>mcp.scorestarling.com/mcp</code> 和 ElevenLabs 那种写法 <code>api.scorestarling.com/v1/mcp</code> 之间犹豫，想知道其他服务实际怎么做，就去查了一圈。</p>
<p>2026年10月2日 17:40 至 17:42（UTC），我们向 27 个服务的 33 个地址各发了一个不带认证信息的 POST 请求：请求体为 <code>{}</code>，带 <code>Accept: application/json, text/event-stream</code> 请求头，超时 8 秒，只记录状态码。401 表示这个地址上有服务响应，并且需要令牌，受保护的 MCP 服务器正是这样开始登录流程的。404 表示这个路径下没有任何服务。有些地址是各服务公开的，另一些是我们为了看哪些路径有响应而尝试的变体和猜测。这只是一些知名服务的快照，不是普查，其中任何一个此后都可能有变化。</p>
<div class="table-scroll" role="region" aria-label="要求登录的 MCP 服务器的地址模式" tabindex="0">
<table>
<caption>返回 401 的 22 个服务的地址模式（2026年10月2日）</caption>
<thead><tr><th scope="col">模式</th><th scope="col" class="num">服务数</th><th scope="col">返回 401 的地址</th></tr></thead>
<tbody>
<tr><th scope="row">mcp. 子域名，路径为 /mcp</th><td class="num">14</td><td><code>mcp.notion.com/mcp</code>, <code>mcp.linear.app/mcp</code>, <code>mcp.sentry.dev/mcp</code>, <code>mcp.supabase.com/mcp</code>, <code>mcp.canva.com/mcp</code>, <code>mcp.higgsfield.ai/mcp</code>, <code>mcp.runwayml.com/mcp</code>, <code>mcp.airtable.com/mcp</code>, <code>mcp.gamma.app/mcp</code>, <code>mcp.posthog.com/mcp</code>, <code>mcp.wix.com/mcp</code>, <code>mcp.intercom.com/mcp</code>, <code>mcp.monday.com/mcp</code>, <code>mcp.paypal.com/mcp</code></td></tr>
<tr><th scope="row">mcp. 子域名，不带路径或带版本号路径</th><td class="num">6</td><td><code>mcp.stripe.com</code>、<code>mcp.vercel.com</code>、<code>mcp.box.com</code>、<code>mcp.miro.com</code>；<code>mcp.atlassian.com/v1/mcp</code>、<code>mcp.asana.com/v2/mcp</code>（Asana 的 <code>mcp.asana.com/sse</code> 也返回了 401）</td></tr>
<tr><th scope="row">api. 子域名，带路径</th><td class="num">2</td><td><code>api.elevenlabs.io/v1/mcp</code>、<code>api.githubcopilot.com/mcp/</code>（GitHub）</td></tr>
<tr><th scope="row">主站或文档站上的路径</th><td class="num">0</td><td>没有；<code>huggingface.co/mcp</code> 和 <code>learn.microsoft.com/api/mcp</code> 返回了 400</td></tr>
<tr><th scope="row">合计</th><td class="num">22</td><td>23 个地址，因为 Asana 在两个地址上都返回了 401</td></tr>
</tbody>
</table>
</div>
<div class="table-scroll" role="region" aria-label="没有返回 401 的 MCP 地址" tabindex="0">
<table>
<caption>没有返回 401 的 10 个地址</caption>
<thead><tr><th scope="col">结果</th><th scope="col">地址</th><th scope="col">说明了什么</th></tr></thead>
<tbody>
<tr><th scope="row">404</th><td><code>mcp.stripe.com/mcp</code>, <code>mcp.vercel.com/mcp</code>, <code>mcp.higgsfield.ai</code>, <code>api.elevenlabs.io/mcp</code></td><td>该路径下没有端点；这几个服务都在同一域名的另一个路径上返回了 401</td></tr>
<tr><th scope="row">400</th><td><code>mcp.figma.com/mcp</code>, <code>huggingface.co/mcp</code>, <code>learn.microsoft.com/api/mcp</code>, <code>docs.mcp.cloudflare.com/mcp</code></td><td>有服务响应，但拒绝了我们的空请求；我们没有深究</td></tr>
<tr><th scope="row">无响应</th><td><code>mcp.suno.com/mcp</code>, <code>mcp.elevenlabs.io/mcp</code></td><td>8 秒内没有任何 HTTP 响应</td></tr>
</tbody>
</table>
</div>
<p>最有用的教训来自那几个 404：MCP 的 URL 无法从域名推断出来。Stripe 和 Vercel 在不带路径的域名上响应，访问 <code>/mcp</code> 反而返回 404；Higgsfield 正好相反；ElevenLabs 则必须带上 <code>/v1</code>。客户端只会原样使用拿到的 URL，而 RFC 9728 把这个 URL 定为服务器元数据必须匹配的身份标识，所以文档、安装链接和插件清单里必须是同一个字符串。插件清单里的写法更是五花八门：在我们装过的插件中，有直接挂在 gitlab.com 主站路径上的 <code>gitlab.com/api/v4/mcp</code>，还有 <code>mcp.hubspot.com/anthropic</code>。这两个我们没有探测。</p>

<h2 id="mcp-endpoint-separate-host">MCP 端点该和网站共用一个域名吗？</h2>
<p>我们认为不该。端点是给软件用的，连接指南是给人看的，两者都想占用那个最直观的地址。2026年10月2日之前，<code>scorestarling.com/mcp</code> 是我们的 MCP 服务器，指南只能放在别处；而且每个客户端都是靠这个 URL 返回的 401 找到登录入口的，它不可能同时又是一个网页。</p>
<p>我们考虑过在同一个 URL 上同时提供两者，给浏览器返回网页、给客户端返回 MCP，但没有把它当作长期方案：对请求的判断只要错一次，所有客户端都会找不到登录入口。传输规范还允许客户端向端点发送 GET 请求来打开事件流，期待的回复是事件流或 405，而不是一个 HTML 页面。</p>
<p>单独的域名还能让网站的改动影响不到端点：网站改版挪不动它，适合营销网站的 CDN 缓存或浏览器人机验证也永远不会挡在它前面。AI 助手是从自己的基础设施而不是浏览器调用服务器的：Anthropic 的连接器文档说，Claude 从 Anthropic 的云端发起连接，身份提供方前面的防火墙可能导致登录失败。我们看过的两家同行也是这样拆分的（2026年10月2日查看，10月3日复核）：Higgsfield 的指南在 <code>higgsfield.ai/mcp</code>，服务器在 <code>mcp.higgsfield.ai/mcp</code>；ElevenLabs 的指南在 <code>elevenlabs.io/mcp</code>，服务器在 <code>api.elevenlabs.io/v1/mcp</code>。</p>
<p>我们的 MCP 域名只提供 <code>/mcp</code> 和 <code>/.well-known/*</code>。其他路径一律以 308 重定向到网站上相同的路径和查询参数，这样登录、授权同意、页面、下载链接和 Cookie 都留在同一个域名下。网站上的 <a href="https://scorestarling.com/zh-hans/mcp">/mcp</a> 现在是 Claude、ChatGPT 等客户端的连接指南。</p>
<pre><code>$ curl -si "https://mcp.scorestarling.com/login?x=1"
HTTP/2 308
location: https://scorestarling.com/login?x=1</code></pre>

<h2 id="mcp-url-pattern">选 mcp.example.com/mcp、不带路径的域名，还是 api.example.com/v1/mcp？</h2>
<p>三种都可以，看这个域名上还放了什么。MCP 授权规范把 <code>https://mcp.example.com/mcp</code> 和 <code>https://mcp.example.com</code> 都列为合法的标准服务器 URI，并要求使用不带末尾斜杠的形式，除非斜杠有实际含义。传输规范只要求有一个同时接受 POST 和 GET 的端点路径，示例是 <code>https://example.com/mcp</code>；Anthropic 的连接器文档用的则是 <code>https://mcp.example.com/mcp</code>。</p>
<div class="table-scroll" role="region" aria-label="我们权衡过的地址方案" tabindex="0">
<table>
<caption>我们为 ScoreStarling 权衡过的方案</caption>
<thead><tr><th scope="col">方案</th><th scope="col">采用者（2026年10月2日）</th><th scope="col">对我们来说</th></tr></thead>
<tbody>
<tr><th scope="row">mcp. 域名 + /mcp（最终选择）</th><td>14 个服务，包括 Notion、Linear、Canva、Higgsfield 和 Runway</td><td>样本中最常见的模式；我们的服务器本来就在 <code>/mcp</code> 上响应，只需要换域名</td></tr>
<tr><th scope="row">不带路径的 mcp. 域名</th><td>Stripe、Vercel、Box、Miro</td><td>最短，但要改更多代码，还得先多发一个版本</td></tr>
<tr><th scope="row">api. 域名 + /v1/mcp</th><td>ElevenLabs；GitHub 用的是 <code>api.githubcopilot.com/mcp/</code></td><td>适合在这个域名上提供公开开发者 API 的公司；我们没有</td></tr>
</tbody>
</table>
</div>
<p>还有两条小规则。Streamable HTTP 端点不要以 <code>/sse</code> 结尾：Anthropic 的文档说，在 Claude 的连接器表单里，以它结尾的 URL 会选用旧的 SSE 传输方式。另外，除非确有必要，不要加末尾斜杠；返回 401 的 23 个地址里，只有 GitHub 的带了斜杠。</p>

<h2 id="move-mcp-server-url">怎样把 MCP 服务器迁移到新 URL？</h2>
<p>让新旧两个地址并行一段时间，并且每个地址都要能独立完整地工作：有自己的 401、自己的元数据，两种受众的令牌都能用。我们分四步完成迁移，在 2026年10月2日晚（UTC）一小时内全部上线：</p>
<ol>
<li><p>趁还没有任何地方用到，先上线别名支持。用一项配置列出以前的地址，每个都必须是 HTTPS、以 <code>/mcp</code> 结尾。令牌的受众是当前地址或某个别名时验证通过，其他受众一律拒绝。主机白名单（Starlette 的可信主机列表和 MCP SDK 的 DNS 重绑定防护）包含所有地址以及网站本身。没有设置别名时，生产环境的行为与以前完全一样。</p></li>
<li><p>添加新域名，然后在同一次变更中切换地址并设置别名。新子域名先配好 DNS 记录，大约 6 分钟后证书也签发了；在我们的服务器被配置为信任它之前，这个域名一直返回 400。一次配置变更既设置了新的资源 URL，又把旧地址列为别名，所以只重新部署一次就让两者同时生效。如果在第 1 步上线前就这样做，正在运行的代码会拒绝网站自己的域名。</p></li>
<li><p>把所有会被人复制的地方都改指向新地址：连接指南（包括为 Claude、Cursor 和 VS Code 编码好的一键安装链接）、插件清单和文档。</p></li>
<li><p>切换令牌受众，然后停用旧地址。一次数据库迁移让我们的访问令牌钩子改为签发新的受众，CI 在代码上线前就应用了这次迁移；别名还在时，两种受众的令牌都照常可用。随后我们删除别名并重新部署。在那次发布和重新部署之间的几分钟里，网站的 <code>/mcp</code> 对浏览器返回指南，对 MCP 客户端返回服务器。现在它只提供指南，指南原来的地址 <code>/connect</code> 则以 308 重定向过来。</p></li>
</ol>
<p>最容易漏掉的是元数据。RFC 9728 要求其中的 <code>resource</code> 与客户端连接时用的 URL 完全一致，Anthropic 的文档也要求它与用户在 Claude 里填写的 URL 相同。所以在新旧并行期间，每个域名都描述它自己，每个 401 都指向本域名下的元数据文档：</p>
<pre><code>GET https://scorestarling.com/.well-known/oauth-protected-resource/mcp
→ "resource": "https://scorestarling.com/mcp"

GET https://mcp.scorestarling.com/.well-known/oauth-protected-resource/mcp
→ "resource": "https://mcp.scorestarling.com/mcp"</code></pre>
<p>我们没有对 <code>/mcp</code> 本身做重定向。传输规范没有为端点规定重定向行为，而端点 URL 作为资源（resource）和令牌受众，是 OAuth 握手的一部分，所以我们选择在两个地址上同时提供 MCP。其他路径则用 308 而不是 301，这样能保留请求方法，因为 RFC 9110 不允许客户端在收到 308 后把 POST 改成 GET。受众是怎样写进令牌的，见<a href="https://scorestarling.com/zh-hans/blog/remote-mcp-server-oauth">我们关于 MCP OAuth 的笔记</a>。</p>
<p>我们能迁得这么快，是因为 ScoreStarling 当时还处在仅限邀请的小规模试运营阶段，还没有人依赖旧地址。别名删除后，仍配置着 <code>scorestarling.com/mcp</code> 的客户端必须重新连接。按照我们运维手册的预期，已经改用新地址、手里却还是旧受众令牌的客户端，会收到一次 401 <code>invalid_token</code>，随后刷新令牌、拿到新受众；不过我们还没亲眼看到真实的 Claude 或 ChatGPT 客户端这样做。如果已经有真实用户，就保留别名，直到旧地址不再有流量。</p>

<h2 id="what-broke">迁移过程中出了哪些问题？</h2>
<p>过程中出了三个小问题：</p>
<ul>
<li>一个检查脚本用 <code>resource.replace('/mcp', '/.well-known/oauth-protected-resource/mcp')</code> 拼接元数据 URL。旧地址上没问题；换成 <code>https://mcp.scorestarling.com/mcp</code> 后，<code>//mcp.</code> 里的 <code>/mcp</code> 也被匹配到了，结果变成 <code>https://.well-known/oauth-protected-resource/mcp.scorestarling.com/.well-known/oauth-protected-resource/mcp</code>。正确的做法是先解析 URL，再按各部分拼接：协议和主机名，然后是 well-known 段，最后是路径。</li>
<li>地址变长后，在 320 像素宽的屏幕上撑破了指南页的版面，直到我们允许行内代码换行才解决。</li>
<li>用 Railway 命令行工具删除别名变量，只是把改动暂存了起来；直到我们手动重新部署，旧地址都还被接受。</li>
</ul>

<h2 id="mcp-url-checklist">MCP 服务器地址选择清单</h2>
<ul>
<li>给端点一个专用域名，不在上面放任何给人看的内容；在我们的样本中，常见做法是 <code>mcp.</code> 加上你的域名。</li>
<li>路径一次定好。<code>/mcp</code> 最常见，不带路径的域名也合法；不要加末尾斜杠，也不要让路径以 <code>/sse</code> 结尾。</li>
<li>给人看的连接指南放在主站上，用一个猜得到的地址，并在导航里加上链接。</li>
<li>为这个确切的 URL 提供 RFC 9728 元数据：放在 <code>/.well-known/oauth-protected-resource</code> 加上你的路径处，根路径下也放一份，其中的 <code>resource</code> 要等于客户端使用的地址。</li>
<li>把这个 URL 设为令牌受众，并在需要迁移之前就做好别名支持。</li>
<li>MCP 域名上的其他所有路径都用 308 重定向到你的网站，并保留查询字符串。</li>
<li>MCP 域名上不要启用浏览器人机验证和 HTML 缓存。</li>
<li>所有地方（指南、安装链接、插件清单、文档）都只公布同一个字符串，并测试每一处是否一致。</li>
<li>迁移顺序：先做别名支持，再在同一次变更中切换新地址、设置别名，然后改掉每一处引用，切换受众，等旧地址没有流量后再停用。</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/transports">MCP 规范（2025-11-25 版）：传输</a> — Model Context Protocol</li>
<li><a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization">MCP 规范（2025-11-25 版）：授权</a> — Model Context Protocol</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc9728">RFC 9728：OAuth 2.0 受保护资源元数据</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.9">RFC 9110：HTTP 语义，§15.4.9 308 永久重定向</a> — IETF</li>
<li><a href="https://claude.com/docs/connectors/custom/remote-mcp">添加不在目录中的连接器</a> — Anthropic</li>
<li><a href="https://claude.com/docs/connectors/building/authentication">连接器的身份验证</a> — Anthropic</li>
<li><a href="https://higgsfield.ai/mcp">Higgsfield MCP</a> — Higgsfield</li>
<li><a href="https://elevenlabs.io/mcp">ElevenLabs Agents &amp; Creative MCP</a> — ElevenLabs</li>
</ol>
</section><h2>常见问题</h2><h3>MCP 服务器的 URL 一定要以 /mcp 结尾吗？</h3><p>不用。MCP 规范只要求有一个端点路径，并把 <code>https://mcp.example.com</code> 和 <code>https://mcp.example.com/mcp</code> 都列为合法的服务器 URI。在我们 2026年10月2日的样本中，要求登录的 22 个服务里有 18 个用了 <code>/mcp</code> 或 <code>/v1/mcp</code> 这类路径，另外 4 个直接在不带路径的域名上响应。选定一种，公布时一字不差即可。</p><h3>连接指南页和 MCP 端点能共用一个 URL 吗？</h3><p>可以在同一个 URL 上给浏览器返回网页、给 MCP 客户端返回服务器，但除了迁移期间的几分钟，我们没有这样做。只要误判一次请求，客户端就找不到登录入口；而且 Streamable HTTP 客户端可能会向端点发送 GET 请求，期待收到事件流或 405。我们的指南在 scorestarling.com/mcp，服务器则有自己的域名。</p><h3>更换 MCP 服务器的 URL 会让用户断开连接吗？</h3><p>只有停用旧地址时才会。这个 URL 既是 OAuth 的资源（resource），也是令牌的受众（audience），所以旧地址一旦不再响应，仍配置着旧地址的客户端就得重新连接。过渡期内让两个地址同时提供服务：两种受众都接受，每个地址各有自己的元数据，等旧地址没有流量了再停用。</p><h3>向 MCP URL 发 POST 请求，返回 401、404 或 400 分别是什么意思？</h3><p>在我们的调查中，401 表示受保护的端点有响应，并要求提供令牌；404 表示这个路径下什么都没有。400 只能说明某个环节拒绝了我们的空测试请求体，我们没有进一步解读。要正确测试一个服务器，请用真正的 MCP 客户端去连接。</p><h3>MCP 服务器的 URL 末尾要加斜杠吗？</h3><p>最好不加。MCP 授权规范要求实现方使用不带末尾斜杠的形式，除非斜杠有实际含义。在我们调查中返回 401 的 23 个地址里，只有 GitHub 的 <code>api.githubcopilot.com/mcp/</code> 带了斜杠。</p>]]></content:encoded>
    </item>
    <item>
      <title>莫扎特的椋鸟：我们名字的由来</title>
      <link>https://scorestarling.com/zh-hans/blog/mozart-starling</link>
      <guid isPermaLink="true">https://scorestarling.com/zh-hans/blog/mozart-starling</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>故事</category>
      <description>1784年5月27日，莫扎特花 34 克罗伊策买下一只椋鸟，它会唱莫扎特刚写好的 K. 453 终乐章主题，还自己加了一个停顿、唱出一个升 G。我们查证了原始记录。</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">先说结论</h2>
<p>1784年5月27日，莫扎特花 34 克罗伊策买下一只椋鸟。这只鸟会唱他 G 大调第十七钢琴协奏曲（K. 453）终乐章的开头，而这首协奏曲大约六周前才登记进他的作品目录。他在账本上写下了鸟儿唱的版本——在一个音上停顿，把协奏曲里的 G 唱成升 G——还在下面加了一句“Das war schön!”（“真美啊！”）。这只鸟在他身边生活了三年；1787年6月4日它死后，莫扎特把它葬在花园里，还为它写了一首打油诗。</p>
</section>

<h2 id="the-1784-purchase">1784 年买鸟：账本里的那条记录</h2>
<p>1784年5月27日，莫扎特在维也纳买下了这只椋鸟；关于它最早的记录，就是他当天记在账本（也叫支出簿）上的一条。这本账是他那年开始记的，和作品目录同时起笔。维基百科援引 Hermann Abert 的莫扎特传记称，这本支出簿他只记了大约一年，而作品目录一直记到他 1791 年去世。</p>
<p>《新莫扎特全集》（Neue Mozart-Ausgabe，简称 NMA）是莫扎特全部作品的学术校订版，由国际莫扎特基金会在网上发布。收录 K. 453 的那一卷在前言里影印了这条记录。记录很短：最上面一行是日期、“Vogel Stahrl”（椋鸟）和“34 Kr.”；下面是一行五线谱，G 大调，2/2 拍；谱表下方，莫扎特写着“Das war schön!”</p>
<p>“Kr.”是克罗伊策（kreuzer）的缩写，一种当时的硬币，60 克罗伊策合 1 古尔登。NMA 的英译本把它印成“34 Crowns”（34 克朗），那句话则译作“That was nice!”（真不错！）；维基百科则依据 Otto Erich Deutsch 编的莫扎特文献传记引用德文原文，读作克罗伊策，译作“That was beautiful!”（真美啊！），我们的首页用的也是这个译法。该卷 NMA 的编者 Eva 和 Paul Badura-Skoda 说，莫扎特显然很喜欢听这只鸟用哨音唱出协奏曲最后一个乐章的主题，于是买下了它，“哪怕音准可能并不十分干净”。</p>

<h2 id="the-tune-bar-by-bar">曲调对照：K. 453 终乐章主题，逐小节看</h2>
<p>这只鸟唱的是协奏曲终乐章开头那个主题的第一个乐句：一个弱起加四个小节，但有两处改动——在第一个完整小节末尾的 G 上停了一下，又在下一小节把 G 唱成了升 G。莫扎特的记录把这两处都保留了下来。</p>
<p>这首协奏曲是莫扎特为学生芭芭拉（芭贝特）·普洛耶尔写的，1784年4月12日登记进作品目录。终乐章是 2/2 拍的 Allegretto（小快板），由一个朴素、好唱的主题和一组变奏构成，最后以 Presto（急板）收尾。下表把这个主题的开头和账本上那行谱并排列出。</p>
<div class="table-scroll" role="region" aria-label="K. 453 终乐章主题与椋鸟版本对照" tabindex="0">
<table>
<caption>K. 453 终乐章主题开头乐句与椋鸟版本逐小节对照（仅列音高）</caption>
<thead><tr><th scope="col">小节</th><th scope="col">协奏曲（K. 453 终乐章）</th><th scope="col">椋鸟（莫扎特的记录）</th><th scope="col">差别</th></tr></thead>
<tbody>
<tr><th scope="row">弱起</th><td>B C（两个八分音符）</td><td>B C</td><td>无</td></tr>
<tr><th scope="row">1</th><td>D D D G</td><td>D D D G，G 上有延长记号（停顿）</td><td>椋鸟把 G 拖长了</td></tr>
<tr><th scope="row">2</th><td>F♯ F♯ G G</td><td>F♯ F♯ G♯ G♯</td><td>G 唱成了升 G</td></tr>
<tr><th scope="row">3</th><td>A A，接八分音符 A B C A</td><td>相同</td><td>无</td></tr>
<tr><th scope="row">4</th><td>B（二分音符），主题继续</td><td>B（二分音符）；记录到此结束</td><td>无</td></tr>
</tbody>
</table>
</div>
<p class="note">音高依据维基百科对这条记录的转写（引自 Deutsch），以及它依照音乐学家 Daniel Heartz 校正过的曲调。我们把两者都和 NMA 核对过：NMA 影印的记录里有延长记号和升号，而它收录的终乐章乐谱（该卷第 46 页）里两者都没有。协奏曲里还有装饰音、跳音记号和一条圆滑线，账本上那行谱都没有写。</p>
<p>如果把它当作一份扒谱来读，这条记录相当用心。莫扎特对自己的曲子再清楚不过，却仍把听到的停顿和升号照实写下，而不是顺手改掉。这行谱记录的是那一次演唱，而不是作品本身。</p>

<h2 id="how-the-bird-knew-the-tune">这只鸟怎么会唱一首才写了六周的曲子？</h2>
<p>没人知道。文献只记下了买鸟这件事，没记它是怎么学会这段曲调的；几种主要说法讲的先后顺序也不一样：</p>
<ul>
<li>NMA 的前言说，莫扎特先听到这只鸟用哨音唱出这个主题，才因此买下了它。</li>
<li>维基百科的“紫翅椋鸟”条目援引《<cite>Birds Britannica</cite>》（2005）一书，说莫扎特是在一家店里听到它唱出自己六周前写成、还未公开演出过的作品中的一个乐句，才买下了它。</li>
<li>维基百科的“莫扎特的椋鸟”条目则认为顺序正好相反：大概是莫扎特在店里（或别的什么买鸟的地方）把曲子教给了这只鸟。</li>
</ul>
<p>“尚未公开演出”这一点也不确定。NMA 前言引用了莫扎特 1784年6月12日写给父亲的一封信，信中说，芭贝特·普洛耶尔第二天会在普洛耶尔家位于德布林（Döbling）的宅邸举办的音乐晚会上，演奏她的新 G 大调协奏曲。据维基百科协奏曲条目的概述，音乐学家 Michael Lorenz 认为莫扎特可能更早就演奏过这首曲子：1784年4月29日，他与小提琴家 Regina Strinasacchi 在克恩滕门剧院（Kärntnertortheater）举办过一场音乐会。如果真是这样，那么在莫扎特买鸟的四周前，这段曲调就已经公开演奏过了。</p>
<p>椋鸟学得很快：维基百科的椋鸟条目提到，有人见过野生椋鸟只听了一遍就模仿出某个声音。在我们看来，最简单的解释是一位作曲家在鸟店里用口哨吹着自己最新的曲子。但没有任何文献能证明真有其事。</p>

<h2 id="why-starlings-mimic">椋鸟为什么这么会模仿？</h2>
<p>紫翅椋鸟（<em>Sturnus vulgaris</em>）能通过学习掌握鸣声，会把借来的声音编进自己的鸣唱，所以宠物椋鸟学会一段口哨旋律，对这个物种来说再平常不过。雄鸟的一段鸣唱可以持续一分钟甚至更久：先是纯净的哨音，接着是多变的乐段，夹杂着其他鸟的鸣声片段和各种杂音，然后是一串咔嗒声，最后以一阵高亢的鸣唱收尾。维基百科援引了 Chris Feare 1996 年在《<cite>British Birds</cite>》上对这个物种的介绍：经验丰富的椋鸟最多能掌握 35 种多变的鸣唱类型。</p>
<p>Paul Ehrlich、David Dobkin 和 Darryl Wheye 在一篇谈鸣声模仿的文章中，把紫翅椋鸟列为以鸣声模仿著称的物种。他们的解释是曲库的大小：丰富多变的鸣唱能帮雄鸟吸引配偶、震慑对手，而从周围环境借声音——哪怕是狗叫、机器声或人的口哨——就是扩充曲库的一种办法。他们还补充说，模仿者通常会因为自己的音色、节奏或场合不对而露馅，所以听者很少真被骗过。</p>
<p>宠物椋鸟很容易学会人说的话。维基百科援引康拉德·洛伦茨（Konrad Lorenz）的观察：这些话对鸟来说毫无意义，所以它会把词语混在一起，在莫名其妙的时候说出来。长期研究宠物椋鸟的 Meredith West 和 Andrew King 则把椋鸟的模仿能力与它们活跃的社交互动联系在一起。</p>
<p>身体构造也帮了忙。2025 年发表在《<cite>Scientific Reports</cite>》上的一项研究，分析了网上发布的 115 段鸟类模仿《<cite>星球大战</cite>》机器人 R2-D2 的视频，其中 8 段是椋鸟。椋鸟模仿这个机器人复杂、多音调的声音时比鹦鹉更准确，而模仿简单的哔哔声时两者不相上下。作者把这种差异归因于鸣管，也就是鸟类的发声器官：椋鸟能分别控制鸣管的左右两侧，因此可以同时发出两个声音，鹦鹉则不能。</p>
<p>椋鸟听曲调的方式似乎也和我们不一样。2016 年发表在《<cite>PNAS</cite>》上的一项研究中，五只椋鸟学会了区分音高上行和下行的四音音列。可一旦把音列整体稍微升高或降低，或者换一种音色播放，它们的表现就和瞎猜差不多。相反，只要保留每个音的频谱形状（也就是能量在各个频率上的分布），哪怕去掉音高，它们也能轻松得多地认出来。音乐家把旋律听成一串音程，移调之后依然不变；椋鸟听到的则更像是一串声音的形状。这也许正是椋鸟学唱的曲调很像、却又不完全一样的原因之一。这只是我们的解读，并没有人在莫扎特那只鸟身上测量过。</p>

<h2 id="the-funeral-and-the-poem">1787年6月：椋鸟的葬礼和莫扎特的诗</h2>
<p>1787年6月4日，这只椋鸟死了，距莫扎特买下它大约三年。莫扎特郑重其事地把它葬在花园里，还为它写了一首诗。那是一段难熬的日子：一周前的 5月28日，他的父亲利奥波德·莫扎特刚在萨尔茨堡去世。</p>
<p>这场葬礼我们只能从二手资料中得知，主要来自莫扎特的早期传记作者，而且说法各不相同：</p>
<ul>
<li>采访过莫扎特遗孀康斯坦策（Constanze）的尼梅切克（Franz Xaver Niemetschek）提到，莫扎特在他租来的花园里，为一只深爱的椋鸟立了一块像样的墓碑，上面刻有铭文。</li>
<li>康斯坦策的第二任丈夫尼森（Georg Nikolaus Nissen）在 1828 年出版的传记里也是这么讲的：一只心爱的椋鸟死后，莫扎特写了诗，并在租来的花园里为它修了一座像样的坟，立了墓志铭（第 668 页）；他喜爱动物，尤其是鸟（第 670 页）。送葬队伍的说法则另有出处：Rudolf von Lewicki 于 1919年11月在《<cite>Mozarteums-Mitteilungen</cite>》上刊印的尼森札记辑录。尼森在其中写道，莫扎特喜欢狗和鸟，家里总养着几只，他的狗名叫 Puzzipaukel；每当有鸟死去，他就会安排一支送葬队伍，凡是会唱歌的人都得披着大面纱跟在后面，还有一首类似安魂曲的歌和一首诗体墓志铭（第 29 页）。这条札记只说“一只鸟”，并没有点明是椋鸟；Otto Erich Deutsch 在他编的莫扎特文献传记第 225 页引用了这段话的英译。</li>
<li>据 RILM 博客的概述，West 和 King 1990 年发表在《<cite>American Scientist</cite>》上的文章描述的是：送葬者蒙着面纱列队行进，唱着赞美诗，并在墓旁聆听那首诗的朗诵。</li>
</ul>
<p>这首诗的落款是 1787年6月4日，开头是“Hier ruht ein lieber Narr, / Ein Vogel Staar”（“这里长眠着一个可爱的小傻瓜，一只椋鸟”，我们的译文）。诗里哀悼这只正值盛年就被夺走的鸟，坚称它并不坏，只是活泼了些，有时是个惹人爱的小淘气；结尾还拿作者自己开了个玩笑：鸟儿临死时，压根没想到那位“der so schön reimen kann”（押韵押得如此漂亮）的人。这里的 schön，正是三年前莫扎特写在鸟儿曲调下方的那个词。</p>
<p>把这首诗译成英文的 Robert Spaethling 认为它既好笑又苦乐参半，写于莫扎特正处在哀痛之中、又埋头创作《<cite>唐璜</cite>》的时候。民俗学家 Wolfgang Mieder 则认为，莫扎特在某种程度上写的是他自己。West 和 King 指出，宠物椋鸟和主人之间的感情非常深，这让人很容易相信，诙谐背后确有真切的哀伤。</p>

<h2 id="did-the-starling-influence-mozart">椋鸟影响了莫扎特的音乐吗？</h2>
<p>协奏曲没有受它影响——莫扎特在买鸟的六周前就已经写完了；至于说它影响了后来的《<cite>音乐玩笑</cite>》（<cite lang="de">Ein musikalischer Spaß</cite>，K. 522），这只是一个没有文献支持的假说。博物学家、2017 年出版的《<cite>Mozart’s Starling</cite>》一书作者 Lyanda Lynn Haupt 对 NPR 说，从时间上看，“这只鸟启发了协奏曲”的说法可以排除。</p>
<p>提出后一种说法的是 West 和 King。他们在 1990 年的文章中写道，《<cite>音乐玩笑</cite>》里的许多元素“带有椋鸟的声音签名”。时间点确实耐人寻味：1787年6月14日，也就是鸟死后十天，莫扎特把这首作品登记进了作品目录。Haupt 对 NPR 也提出了类似的看法：把这首曲子最古怪的段落和椋鸟的鸣唱叠在一起听，用她的话说，能听出同样“支离破碎的乐句”和同样的顽皮。</p>
<p>这只能算假说。维基百科指出，West 和 King 的依据是唱片封套说明里对这首曲子的描述。许多评论者认为《<cite>音乐玩笑</cite>》是在戏仿蹩脚的作曲家，而目前没有记录表明莫扎特说过这首曲子的用意。我们读过的资料中，没有一份引用过把这首曲子和那只鸟联系起来的莫扎特书信或笔记。就我们所知，莫扎特和他的椋鸟之间唯一有文献可考的音乐联系，就是 1784年5月27日的那条账本记录。</p>

<h2 id="why-we-named-scorestarling-after-it">我们为什么用它给 ScoreStarling 命名</h2>
<p>我们用这只鸟给产品命名，因为它的故事正是我们在做的事。一句话概括：鸟儿在唱，作曲家把听到的写了下来。名字是 Score（乐谱）加 Starling（椋鸟），我们的标志是一只椋鸟，扬起的翅膀正好构成音符的符尾。那个不太为人所知的细节，对我们同样重要：莫扎特的那行谱忠实记下了他听到的声音，而他听到的，和他自己写的并不完全一样。扒谱只是初稿，最后要靠耳朵来完成。</p>
<p>ScoreStarling 会听录音，把它写成 MIDI 和可编辑的乐谱。你可以对照原录音播放、循环播放某一段、修改音符，然后导出。它也能读取印刷乐谱、乐谱文件以及 ABC 或 MusicXML 文本，并以五线谱、吉他六线谱或简谱显示。扒单件乐器、人声或钢琴独奏以及编辑乐谱都是免费的；扒乐队、解锁乐谱的 MusicXML 和 MIDI 需要消耗积分。每个账号每月免费获得 100 积分，其余细节见<a href="https://scorestarling.com/zh-hans/how-it-works#specs">规格说明</a>。我们的<a href="https://scorestarling.com/zh-hans/blog/ai-music-transcription-accuracy">准确率文章</a>写明了我们测了什么、用的是哪些录音。</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">参考资料</h2>
<ol>
<li><a href="https://dme.mozarteum.at/DME/objs/pdf/nma_153_-23_-3_eng.pdf">Neue Mozart-Ausgabe V/15/5，Eva 和 Paul Badura-Skoda 撰写的前言（1965），英译本</a>——Internationale Stiftung Mozarteum，Digital Mozart Edition</li>
<li><a href="https://dme.mozarteum.at/DME/nma/nma_cont.php?vsep=153&amp;gen=edition&amp;l=2&amp;p1=46">Neue Mozart-Ausgabe V/15/5：G 大调协奏曲 K. 453 第三乐章，乐谱第 46 页</a>——Internationale Stiftung Mozarteum，Digital Mozart Edition</li>
<li><a href="https://kv.mozarteum.at/en/work/ein-musikalischer-spass-a-musical-joke-for-2-horns-2-violins-viola-and-basso-6036">KV 522, Ein musikalischer Spaß</a>——克歇尔目录在线版，Internationale Stiftung Mozarteum</li>
<li><a href="https://en.wikipedia.org/wiki/Mozart%27s_starling">Mozart’s starling</a>——英文维基百科（引用 Deutsch 1965、Heartz 2009、Abert 2016、West 和 King 1990、Spaethling 2000、Mieder 2003 及 Niemetschek）</li>
<li><a href="https://en.wikipedia.org/wiki/Piano_Concerto_No._17_(Mozart)">Piano Concerto No. 17 (Mozart)</a>——英文维基百科（引用 Lorenz 2006）</li>
<li><a href="https://en.wikipedia.org/wiki/Common_starling">Common starling</a>——英文维基百科（引用 Feare 1996，载于《<cite>British Birds</cite>》；Cocker 和 Mabey 2005；Lorenz 1961）</li>
<li><a href="https://en.wikipedia.org/wiki/A_Musical_Joke">A Musical Joke</a>——英文维基百科</li>
<li><a href="https://en.wikipedia.org/wiki/Leopold_Mozart">Leopold Mozart</a>——英文维基百科</li>
<li><a href="https://en.wikipedia.org/wiki/Kreuzer">Kreuzer</a>——英文维基百科</li>
<li><a href="https://digibib.mozarteum.at/ismretroverbund/periodical/pageview/2498147">Aus Nissens Kollektaneen</a>——Rudolf von Lewicki，《<cite>Mozarteums-Mitteilungen</cite>》第 2 卷第 1 期，萨尔茨堡，1919年11月，第 28–30 页（扫描件见国际莫扎特基金会的 Bibliotheca Mozartiana digital）</li>
<li><a href="https://books.google.com/books?id=e8AtwaddUW4C&amp;pg=PA225">Mozart: A Documentary Biography</a>——Otto Erich Deutsch，Stanford University Press，1966 年版，第 225 页（引用 Rudolf von Lewicki 刊印于《<cite>Mozarteums-Mitteilungen</cite>》1919年11月号的尼森札记）</li>
<li><a href="https://archive.org/details/bub_gb_BRRbAAAAQAAJ">Biographie W. A. Mozart’s</a>——Georg Nikolaus von Nissen，Leipzig: Breitkopf &amp; Härtel，1828，第 668 页和第 670 页（扫描件见互联网档案馆）</li>
<li><a href="https://bibliolore.org/2017/09/14/mozarts-starling/">Mozart’s starling</a>——Bibliolore，RILM 博客（2017），概述 West 和 King 的“Mozart’s Starling”，载于《<cite>American Scientist</cite>》第 78 卷第 2 期（1990）：106–114</li>
<li><a href="https://www.npr.org/sections/deceptivecadence/2017/04/20/524349771/how-do-you-bond-with-mozart-adopt-a-starling">How Do You Bond With Mozart? Adopt A Starling</a>——Tom Huizenga，NPR（2017年4月20日）</li>
<li><a href="https://web.stanford.edu/group/stanfordbirds/text/uessays/uVocal_Copying.html">Vocal Copying</a>——Paul R. Ehrlich、David S. Dobkin 和 Darryl Wheye，Birds of Stanford（1988）</li>
<li><a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC12592491/">What imitating an iconic robot reveals on allospecific vocal imitation in parrots and starlings</a>——Nick C. P. Dam、Henkjan Honing 和 Michelle J. Spierings，《<cite>Scientific Reports</cite>》（2025）</li>
<li><a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC4760803/">Songbirds use spectral shape, not pitch, for sound pattern recognition</a>——Micah R. Bregman、Aniruddh D. Patel 和 Timothy Q. Gentner，《<cite>PNAS</cite>》第 113 卷（2016）</li>
</ol>
</section><h2>常见问题</h2><h3>莫扎特的椋鸟唱的是哪首曲子？</h3><p>莫扎特 G 大调第十七钢琴协奏曲（K. 453）终乐章（Allegretto，小快板）主题的第一个乐句。莫扎特在 1784年4月12日把这首协奏曲登记进作品目录；5月27日买下这只鸟时，他在账本上记下了它唱的版本：同样是一个弱起加四个小节，只是第 1 小节末尾的 G 上多了一个停顿，第 2 小节的 G 唱成了升 G。</p><h3>莫扎特账本上的“Das war schön”是什么意思？</h3><p>意思是“真美啊”或“真好听”；《新莫扎特全集》（Neue Mozart-Ausgabe）前言的英译本译作“That was nice!”（真不错！）。1784年5月27日，莫扎特在账本上记下这只鸟（“Vogel Stahrl”）和它的价钱 34 克罗伊策，并把这句话写在椋鸟版本的曲调下方。</p><h3>莫扎特的椋鸟后来怎么样了？</h3><p>它在莫扎特家生活了大约三年，1787年6月4日死去，比莫扎特的父亲利奥波德晚走一周。莫扎特把它葬在花园里，还为它写了一首打油诗，落款就是这一天，开头是“Hier ruht ein lieber Narr, / Ein Vogel Staar”（“这里长眠着一个可爱的小傻瓜，一只椋鸟”）。莫扎特的早期传记作者尼森（Georg Nikolaus Nissen）写道，莫扎特在租来的花园里为这只心爱的椋鸟修了一座像样的坟墓，立了墓志铭，还说莫扎特喜爱动物，尤其是鸟；至于蒙着面纱的送葬者，则出自尼森的私人笔记，1919 年才首次刊印，并不在那本传记里。</p><h3>椋鸟真的会学唱曲子吗？</h3><p>会。紫翅椋鸟擅长模仿，会把别的鸟的鸣唱和其他声音编进自己的歌里；养在家里的椋鸟也会跟身边的人学说话、学曲调。它们学得很像，但不完全一样：莫扎特的鸟多了一个停顿，还把 G 唱成了升 G；2016 年的一项实验室研究发现，椋鸟靠频谱形状而不是音高来辨认音列。</p>]]></content:encoded>
    </item>
  </channel>
</rss>
