<?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 blog</title>
    <link>https://scorestarling.com/blog</link>
    <description>Research, guides and stories about turning recordings into editable sheet music.</description>
    <language>en</language>
    <lastBuildDate>Thu, 08 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://scorestarling.com/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <image>
      <url>https://scorestarling.com/assets/starling-v2.png</url>
      <title>ScoreStarling blog</title>
      <link>https://scorestarling.com/blog</link>
    </image>
    <item>
      <title>How guitar TAB chooses strings and frets</title>
      <link>https://scorestarling.com/blog/guitar-tab-strings-and-frets</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/guitar-tab-strings-and-frets</guid>
      <pubDate>Thu, 08 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Guide</category>
      <description>One note can sit in six places on a guitar neck. How MuseScore picks one, when that reads badly, and why TAB from a MusicXML file can come out an octave high.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>A pitch can usually be played on two to six strings, so TAB has to choose one. Most notation software, MuseScore included, takes the highest string that can play each note, which is its lowest fret, and moves a chord note whose string is taken to the next free string. That gives low, readable fingerings but can make a melody climb one string instead of staying in a hand position. When TAB from a file comes out an octave too high, the file wrote the guitar an octave above its sound and the app fretted the written pitch.</p>
</section>

<h2 id="why-several-places">Why one note has several places on the neck</h2>
<p>Because the strings overlap. In standard tuning the open strings are E2, A2, D3, G3, B3 and E4, five half steps apart except G to B, which is four, so the 5th fret of one string (the 4th on the G string) plays the next string’s open note. The E above middle C, E4, is the open top string, the 5th fret of the B string, the 9th of G, the 14th of D, the 19th of A and the 24th of the low E: six places on a 24-fret neck, five on a 22-fret electric or a 19-fret classical guitar.</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">nut</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">frets</text>
</svg>
<figcaption>The six places E4 sounds in standard tuning: open on the top string, then frets 5, 9, 14, 19 and 24 on the strings below it.</figcaption>
</figure>
<p>A staff only gives the pitch. TAB gives the string and fret, so whoever writes it, a person or a program, picks one of those places for every note, and the choice decides where the hand goes.</p>

<h2 id="what-rule-apps-use">What rule does notation software use?</h2>
<p>MuseScore’s is in its source code, in <code>StringData</code>. A note goes to the highest string whose open pitch is at or below it (“using the highest possible string”, says the comment above <code>convertPitch</code>). For a chord, the notes are taken from the highest down; when a note’s string is already used, the app looks for “a suitable string, from topmost”: the first free string from the top that can play the note within the neck’s frets. If none can, both notes stay on one string and the TAB marks the clash.</p>
<p>We wrote the same rule for ScoreStarling and compared the two. A script wrote 120 random chords of one to four different notes between E2 and C6 on a standard-tuned TAB staff with no frets in it, let the MuseScore 4.7.5 command line fill them in, and compared every note. We ran it four times with different random chords.</p>
<div class="table-scroll" role="region" aria-label="ScoreStarling’s default strings and frets against MuseScore’s on 480 random chords" tabindex="0">
<table>
<caption>480 random chords, MuseScore 4.7.5 against ScoreStarling’s default, October 8, 2026</caption>
<thead><tr><th scope="col">Chords</th><th scope="col" class="num">Count</th><th scope="col">Result</th></tr></thead>
<tbody>
<tr><th scope="row">MuseScore could play</th><td class="num">447</td><td>The same string and fret for all 1,055 notes</td></tr>
<tr><th scope="row">MuseScore left two notes on one string</th><td class="num">13</td><td>High chords; its imported guitar has fewer frets than our 24, and we put each note on its own string</td></tr>
<tr><th scope="row">No string order can play them</th><td class="num">20</td><td>More low notes than strings that reach them, such as two notes below A2, which only the low E string plays; written as MuseScore writes them and marked</td></tr>
</tbody>
</table>
</div>
<p>The rule is predictable, which is why it’s a good default: anyone reading the TAB can work out where a note will be. A Guitar Pro or MuseScore user who opens the score sees the same frets.</p>

<h2 id="when-lowest-reads-badly">When does the lowest-fret rule read badly?</h2>
<p>When a line sits high on the neck. The rule never looks at the note before or after, so every note that the top string can play goes there, and a phrase a player would keep in one place runs up and down that one string. Here is a phrase coming down from the E at the 12th fret, as ScoreStarling writes it under each rule.</p>
<figure class="tab-compare">
  <div class="tab-compare-row"><p class="tab-compare-name">Lowest frets</p><div class="notation-sheet"><!-- engrave:phrase-lowest --><svg class="engraved" viewBox="0 0 1100 313" role="img" aria-label="The phrase as TAB with each note at its lowest fret: all on the top string, from the 12th fret down to the 3rd" 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">Fewest hand shifts</p><div class="notation-sheet"><!-- engrave:phrase-hand --><svg class="engraved" viewBox="0 0 1100 313" role="img" aria-label="The phrase as TAB with fewest hand shifts: the last two notes on the B string at the 10th and 8th frets" 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>The same eight notes as ScoreStarling draws them under each rule. The two notes in red move to the B string, so the hand stays between the 7th and 12th frets.</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="A descending phrase under two string rules" tabindex="0">
<table>
<caption>A phrase from C5 in standard tuning: string and fret, string 1 being the top string</caption>
<thead><tr><th scope="col">Note</th><th scope="col">Lowest frets</th><th scope="col">Fewest hand shifts</th></tr></thead>
<tbody>
<tr><th scope="row">C5</th><td>1st string, 8</td><td>1st string, 8</td></tr>
<tr><th scope="row">D5</th><td>1st string, 10</td><td>1st string, 10</td></tr>
<tr><th scope="row">E5</th><td>1st string, 12</td><td>1st string, 12</td></tr>
<tr><th scope="row">D5</th><td>1st string, 10</td><td>1st string, 10</td></tr>
<tr><th scope="row">C5</th><td>1st string, 8</td><td>1st string, 8</td></tr>
<tr><th scope="row">B4</th><td>1st string, 7</td><td>1st string, 7</td></tr>
<tr><th scope="row">A4</th><td>1st string, 5</td><td>2nd string, 10</td></tr>
<tr><th scope="row">G4</th><td>1st string, 3</td><td>2nd string, 8</td></tr>
</tbody>
</table>
</div>
<p>The first column covers frets 3 to 12 on one string, so the hand moves again and again on the way down. The second plays the last two notes on the B string at the 10th and 8th frets and stays between the 7th and the 12th. Neither is wrong, and the lowest-fret version is what most notation apps would show; the second is closer to what a teacher would write.</p>
<p>A program can find the second kind by planning the whole part. ScoreStarling’s “fewest hand shifts” option treats each note or chord as a step, lists the ways it can be fretted, and searches for the cheapest path through all of them: the hand covers four frets, a move costs more the farther it goes, a chord wider than four frets costs extra, higher positions and frets above the 12th cost a little, and open strings are free. It still doesn’t know your technique, so a planned fingering is a suggestion to check.</p>

<h2 id="octave-too-high">Why is my TAB an octave too high?</h2>
<p>Because a guitar sounds an octave below its written notes, and a file can say that in two ways. MusicXML can write the part at the pitch it sounds, under a treble clef with a small 8 below it, or write it an octave up with a <code>transpose</code> element, which “represents what must be added to a written pitch to get a correct sounding pitch”. Both print the same staff. An app that fills in TAB from the written pitch of the second kind puts every note an octave too high.</p>
<p>We saw it in MuseScore 4.7.5. A file held an open-position C major scale, C3 to C4 as it sounds (from the A string’s 3rd fret to the B string’s 1st), written the second way with its strings and frets in it. MuseScore replaced every fret and wrote the scale from the B string’s 1st fret to the top string’s 8th, the same scale an octave up. Written at the pitch it sounds under the 8 clef, the same scale opened with every string and fret as written. ScoreStarling’s MusicXML downloads write TAB parts that way.</p>
<p class="note">If a TAB you were given sits an octave high, check the clef and any transposition of the part in the source file before you change notes.</p>

<h2 id="capo">What does a capo change in a file?</h2>
<p>The open notes. MusicXML’s <code>capo</code> element “changes the open tuning of the strings” by its number of half steps, and fret numbers start at 0 for an open string, so with a capo at the 2nd fret, fret 0 is the string played at the capo and fret 3 is the 5th fret from the nut. That matches what players read on a capo chart. MuseScore 4.7.5 ignored the capo when it opened our MusicXML and wrote frets from the nut, so print the capo position in the score’s text as well.</p>

<h2 id="scorestarling">How ScoreStarling writes TAB</h2>
<p>From October 8, 2026, ScoreStarling shows a guitar, bass or ukulele part with TAB under the staff or as TAB alone, from a recording or a video link, a PDF of sheet music or a MusicXML, MIDI or ABC file. The default strings follow the lowest-fret rule above; “fewest hand shifts” plans one hand position; you can move any note to another string at the same pitch or type a fret. Tunings include drop D, DADGAD and open G, with a capo up to the 12th fret. Notes no string can play as written are marked. It doesn’t write bends, slides, hammer-ons or strumming, and the tuning stays the same for the whole piece. Try it on <a href="https://scorestarling.com/audio-to-guitar-tab">audio to guitar tab</a>, <a href="https://scorestarling.com/youtube-to-guitar-tab">YouTube to guitar tab</a> or <a href="https://scorestarling.com/sheet-music-to-guitar-tab">sheet music to guitar tab</a>.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">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> and <code>fretChords</code>, read October 7, 2026.</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/string/">MusicXML 4.0, the string element</a>: string 1 is the highest-pitched full-length string.</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/fret/">MusicXML 4.0, the fret element</a>: 0 for an open string.</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/capo/">MusicXML 4.0, the capo element</a>.</li>
<li><a href="https://musicxml.formats.music/musicxml-reference/elements/transpose/">MusicXML 4.0, the transpose element</a>.</li>
<li>Our comparison: 480 random chords in standard tuning, MuseScore Studio 4.7.5’s command line against ScoreStarling, and the C major scale written both ways, October 8, 2026.</li>
</ol>
</section><h2>Questions and answers</h2><h3>Why does my notation app put a note on a string I wouldn’t use?</h3><p>Because a staff doesn’t say which string to use, so the app applies a rule. MuseScore takes the highest string that can play the note, which is its lowest fret, and gives a chord note whose string is taken to the next free one. Change the notes you play elsewhere; the pitch stays the same.</p><h3>Why is my guitar TAB an octave too high?</h3><p>The file probably writes the guitar an octave above its sound, with a transpose element, and the app fretted the written pitch. Writing the part at the pitch it sounds under a treble clef with an 8 below fixes it. In our test MuseScore 4.7.5 read an open-position C major scale written the first way as the same scale an octave up on the top two strings.</p><h3>With a capo, does fret 0 mean the nut or the capo?</h3><p>In MusicXML, the capo: the capo element raises the open tuning of the strings by its number of half steps, so fret 0 is the string played at the capo. MuseScore 4.7.5 ignored the capo when it opened our MusicXML and counted frets from the nut.</p><h3>Can ScoreStarling make guitar TAB from a recording?</h3><p>Yes, since October 8, 2026: it writes the recording on a staff with TAB under it, or TAB alone, for guitar, bass and ukulele, free for one instrument. It doesn’t write bends, slides or hammer-ons. See <a href="https://scorestarling.com/audio-to-guitar-tab">audio to guitar tab</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Does MP3 hurt music transcription? We measured WAV vs MP3</title>
      <link>https://scorestarling.com/blog/mp3-vs-wav-music-transcription</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/mp3-vs-wav-music-transcription</guid>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Research</category>
      <description>Three transcription engines, the same recordings as WAV, MP3 and AAC. At 320 kbit/s, MP3 moved onset F1 by at most 0.002 and kept every file’s length.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>Not at 320 kbit/s. On October 5, 2026 we transcribed the same recordings as lossless audio and after a round trip through MP3 and AAC, with three engines: Spotify’s Basic Pitch on 12 guitar recordings, a solo-piano model on 50 piano performances and Mirelo on a band song. MP3 at 320 kbit/s moved each engine’s onset F1 by at most 0.002 on a scale of 0 to 1, a handful of notes in a few thousand, and kept every file’s length to the sample. MP3 at 192 kbit/s and AAC at 256 kbit/s moved scores by up to 0.008, up as often as down, and the AAC files came back up to 733 samples longer.</p>
</section>

<h2 id="why-ask">Why would MP3 change the notes?</h2>
<p>MP3 and AAC throw away detail the ear is unlikely to notice, mostly quiet sounds next to louder ones and high frequencies. Models such as Basic Pitch read a time-frequency picture of the audio, and nothing guarantees they ignore the same detail a listener does. A note near a model’s threshold can appear or vanish when the spectrum shifts a little.</p>
<p>Earlier work points to a small effect. Urbano and colleagues found MFCC features stable under lossy encoding from about 160 kbit/s, and chroma features, which follow pitch classes, hardly affected by codec or bitrate at all (ISMIR 2014). They measured features, not transcribed notes, so we measured the notes.</p>
<p>We had a practical reason too. Lossless copies of recordings and of their playback made up 77% of our score storage, and we wanted to keep smaller copies only if they cost nothing in accuracy.</p>

<h2 id="how-we-tested">How we tested</h2>
<p>Every recording went through each codec with FFmpeg 8.1.1 (LAME at a constant 320 or 192 kbit/s, FFmpeg’s own AAC encoder at 256 kbit/s), then was decoded by the same code that reads an upload. A control changed the lossless audio by 0.01 dB, which nobody can hear, to show how much the measures move with no codec at all. All three engines are deterministic: the same input gives the same notes.</p>
<div class="table-scroll" role="region" aria-label="What each engine was tested on" tabindex="0">
<table>
<caption>The engines and recordings</caption>
<thead>
<tr><th scope="col">Engine</th><th scope="col">Recordings</th><th scope="col">Reference notes</th></tr>
</thead>
<tbody>
<tr><th scope="row">Basic Pitch, as ScoreStarling runs it for one instrument</th><td>12 GuitarSet recordings (a solo and an accompaniment from each of six players), the first 60 seconds of a Slakh2100 band song, and a 58-second band piece we wrote and synthesized</td><td>The datasets’ annotations</td></tr>
<tr><th scope="row">Kong and colleagues’ solo-piano model</th><td>25 piano pieces (24 public-domain scores and one we wrote), each played twice with different rubato and rendered with a SoundFont piano and sustain pedal: 50 performances</td><td>The notes played</td></tr>
<tr><th scope="row">Mirelo’s Audio-to-MIDI (a2m-1.1)</th><td>The same 60-second Slakh band song, lossless and MP3 320 only</td><td>The song’s MIDI</td></tr>
</tbody>
</table>
</div>
<p>Onset F1 counts a transcribed note as right when its pitch is within 50 cents and its start within 50 ms of a reference note, and it punishes missed and extra notes alike; 1.0 is perfect. Our <a href="https://scorestarling.com/blog/ai-music-transcription-accuracy">accuracy article</a> explains the measure in full.</p>

<h2 id="basic-pitch-results">What changed for one instrument?</h2>
<p>Almost nothing. Across the 12 guitar recordings Basic Pitch’s onset F1 stayed between 0.761 and 0.764 in every format, while the stereo file shrank to between 14% and 23% of the WAV.</p>
<figure class="bars-figure">
<div class="bars" aria-hidden="true">
<p class="bars-title">Onset F1, 12 guitar recordings <span>0 to 1, higher is better</span></p>
<div class="bar-row"><span>Lossless</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>Control</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">Size of a stereo recording <span>share of the WAV</span></p>
<div class="bar-row"><span>Lossless</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>Control</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>The same 12 GuitarSet recordings in five formats: onset F1 barely moves while the file shrinks to a quarter or less. Values are in the table below.</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Basic Pitch results by format" tabindex="0">
<table>
<caption>Basic Pitch as ScoreStarling runs it, by format (October 5, 2026)</caption>
<thead>
<tr><th scope="col">Measure</th><th scope="col" class="num">Lossless</th><th scope="col" class="num">Control</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">Onset F1, guitar</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">Onset and offset F1, guitar</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">Onset F1, Slakh song</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">Onset F1, our band piece</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">Notes found, of 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">Found notes that changed</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">Key as annotated</th><td class="num">10 of 12</td><td class="num">10 of 12</td><td class="num">10 of 12</td><td class="num">10 of 12</td><td class="num">10 of 12</td></tr>
<tr><th scope="row">Tempo unchanged</th><td class="num">—</td><td class="num">14 of 14</td><td class="num">14 of 14</td><td class="num">14 of 14</td><td class="num">14 of 14</td></tr>
<tr><th scope="row">Length change, samples</th><td class="num">—</td><td class="num">0</td><td class="num">0</td><td class="num">+16 to +733</td><td class="num">0</td></tr>
<tr><th scope="row">Signal-to-noise, dB (median)</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">Size, mono / stereo</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">The lossless score is 0.763, not the 0.744 in our October 3 article: since October 4, ScoreStarling writes one instrument as one line, which changed five of the solos. Both runs use Basic Pitch 0.4.0.</p>

<h2 id="piano-results">And for solo piano?</h2>
<p>The piano model heard the same notes in every format. Its onset F1 against the notes played was 0.9427 lossless, 0.9426 for the control, 0.9428 for MP3 320 and 0.9433 for MP3 192, and it found about 92.2% of the written notes in every format.</p>
<p>The step that reads beats and bar lines from those notes was less steady, and not because of the codec. It placed the bar lines differently in 5 of the 50 performances after the inaudible control, and in 7 after MP3 320, four of them the same performances as the control. That step is sensitive to any change at all, so we treat it as noise in the measure rather than a cost of MP3. AAC was not run on piano.</p>

<h2 id="band-results">And for a full band?</h2>
<p>One paid comparison with Mirelo, on the 60-second band song: 842 notes and drum strokes came back for both the lossless file and the MP3, and 829 of them (98.5%) started within 50 ms of the lossless run’s.</p>
<div class="table-scroll" role="region" aria-label="Mirelo results, lossless and MP3" tabindex="0">
<table>
<caption>Mirelo Audio-to-MIDI on one 60-second band song</caption>
<thead>
<tr><th scope="col">Measure</th><th scope="col" class="num">Lossless</th><th scope="col" class="num">MP3 320</th></tr>
</thead>
<tbody>
<tr><th scope="row">Onset F1, pitched notes (703 in the song)</th><td class="num">0.6707</td><td class="num">0.6687</td></tr>
<tr><th scope="row">Drum onset F1 (423 strokes)</th><td class="num">0.6983</td><td class="num">0.7023</td></tr>
<tr><th scope="row">Bass / guitar / piano onset 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">Tempo, BPM</th><td class="num">133.0</td><td class="num">133.0</td></tr>
</tbody>
</table>
</div>

<h2 id="why-notes-moved">Why did any notes change?</h2>
<p>Because any change to the input moves a few notes that sit right at a model’s threshold. Of about 2,740 notes Basic Pitch found, the inaudible control moved one, MP3 320 moved 10, AAC 17 and MP3 192 14, never more than 7 in one recording. MP3 320 came out a hair lower in three of the five comparisons (by 0.0013 to 0.0020), level in one and a hair higher in one. AAC and MP3 192 went up on the guitar or the Slakh song and down on our synthesized band piece, by up to 0.008. A codec that cost real accuracy would show up as a clear drop everywhere, and none did.</p>
<p>What a codec can change for certain is timing at the edges. AAC encoders add silent priming samples at the start and pad the end to a whole frame; Apple documents a delay of 2112 samples. Our AAC files decoded 16 to 733 samples longer. FFmpeg writes MP3 with a Xing/LAME header that records the encoder delay, and our MP3 files decoded to the exact original length. That matters when a score is played in sync with its recording.</p>

<h2 id="which-file">Which file should you upload?</h2>
<ul>
<li>The file you already have. Converting a WAV to MP3 first gains nothing, and converting an MP3 back to WAV restores nothing.</li>
<li>If you must compress, MP3 at 320 kbit/s matched the original most closely by signal-to-noise and kept the exact length.</li>
<li>Low bitrates are unmeasured. We stopped at 192 kbit/s, so check what your recording app saves.</li>
<li>Recording quality matters far more than the format: one clear part, little reverb, no background music. Our <a href="https://scorestarling.com/blog/voice-memo-to-sheet-music">voice memo guide</a> covers what to check.</li>
</ul>
<p>ScoreStarling acts on this result: since October 5, 2026 a WAV, AIFF or FLAC upload is kept as a 320 kbit/s MP3 and transcribed from that copy, which stores a stereo recording at 23% of its WAV size. MP3, M4A, Ogg and video files are kept as they came.</p>

<h2 id="limits">What this test can’t tell you</h2>
<ul>
<li>One band song for Mirelo, and synthesized piano rather than recorded pianos.</li>
<li>No singing. Our real recorded takes are private and were already AAC files, so they could not serve as a lossless reference.</li>
<li>One encoder per format and no bitrate below 192 kbit/s.</li>
<li>Onset F1 says whether a note was heard, not whether the written score reads well.</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">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 and Serra, ISMIR 2014</li>
<li><a href="https://zenodo.org/records/3371780">GuitarSet 1.1.0</a> — Xi, Bittner, Pauwels, Ye and Bello, Zenodo (CC BY 4.0)</li>
<li><a href="https://zenodo.org/records/4599666">Slakh2100</a> — Manilow, Wichern, Seetharaman and 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 et al., 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 and Wang, 2020</li>
<li><a href="https://mir-eval.readthedocs.io/latest/api/transcription.html">mir_eval.transcription</a> — mir_eval documentation</li>
<li><a href="https://ffmpeg.org/ffmpeg-formats.html#mp3">FFmpeg formats: the mp3 muxer</a> — FFmpeg documentation</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>Questions and answers</h2><h3>Should I upload WAV or MP3 for transcription?</h3><p>Either. In our October 5, 2026 test an MP3 at 320 kbit/s scored within 0.002 onset F1 of the WAV it came from, with three different engines. Upload the file your recorder or editor gives you, and skip converting it. An MP3 at 192 kbit/s and an AAC file at 256 kbit/s stayed within 0.008; lower bitrates were not tested.</p><h3>Does a lower MP3 bitrate change the notes?</h3><p>A few, in both directions. At 192 kbit/s, 14 of about 2,740 matched notes changed across 14 recordings, against 10 at 320 kbit/s and 1 for an inaudible volume change. Onset F1 moved by up to 0.008 at 192 kbit/s and by at most 0.002 at 320 kbit/s. Bitrates below 192 kbit/s were not measured.</p><h3>Why does an AAC file come out slightly longer than the WAV?</h3><p>AAC encoders add silent priming samples at the start and pad the end to a whole frame; Apple documents a priming delay of 2112 samples. If the player or decoder does not trim them, the audio is a little longer. In our test AAC files decoded 16 to 733 samples longer. MP3 written by FFmpeg with its Xing/LAME header decoded to the exact length.</p><h3>Does ScoreStarling convert my WAV to MP3?</h3><p>Yes. Since October 5, 2026 a WAV, AIFF or FLAC upload is kept as a 320 kbit/s MP3, the setting measured here, and transcribed from that copy. MP3, M4A, Ogg and video uploads are kept as they came.</p>]]></content:encoded>
    </item>
    <item>
      <title>Our MCP App in ChatGPT on mobile: what we learned</title>
      <link>https://scorestarling.com/blog/mcp-app-chatgpt-mobile</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/mcp-app-chatgpt-mobile</guid>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Engineering</category>
      <description>On a phone, ChatGPT counted its message box in a bottom inset of about 127 px and rebuilt our MCP App from old results. Four bugs, fixes and tests that catch them.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>On a phone, ChatGPT lays its title bar and message box over a fullscreen MCP App and reports them as safe-area insets, about 47 px and 127 px in our run, and moves the bottom one while the app is open. It also rebuilt our panel from the conversation’s original tool result whenever it entered or left fullscreen. Keep the inset clear without adding your own allowance, place script-positioned elements again on every context change, and check on load whether a result is out of date. We tested ChatGPT’s mobile website in an iPhone Simulator, not the native app.</p>
</section>

<h2 id="what-broke-on-a-phone">What broke when we opened our MCP App in ChatGPT on a phone?</h2>
<p>Four things, all involving fullscreen. ScoreStarling’s panel shows a score in the chat (<a href="https://scorestarling.com/mcp">how to connect it</a>); in fullscreen you tap notes and ask the chat about them from a selection card that docks above the player on a phone. On October 7, 2026 we ran it in ChatGPT’s mobile website, in Safari on the iOS Simulator’s iPhone 17 (402 × 874 points). The panel’s frame is cross-origin, so we read it from screenshots.</p>
<div class="table-scroll" role="region" aria-label="Problems found in ChatGPT on a phone" tabindex="0">
<table>
<caption>Found on ChatGPT’s mobile website, October 7, 2026</caption>
<thead><tr><th scope="col">What we saw</th><th scope="col">Cause</th><th scope="col">Fix</th></tr></thead>
<tbody>
<tr><th scope="row">The player floated about a third of the way up the screen</th><td>A fixed 96 px on top of a bottom inset (about 127 px) that already covers the message box</td><td>The inset or 96 px, whichever is larger</td></tr>
<tr><th scope="row">A longer draft or the Thinking bar raised the player, not the docked card</th><td>ChatGPT moves the inset; the player follows in CSS, the card is placed by script</td><td>Place the card on each context change</td></tr>
<tr><th scope="row">After an edit, entering or leaving fullscreen showed the old score, read-only</th><td>ChatGPT built a new panel from the original tool result</td><td>Read the current version if a result is behind</td></tr>
<tr><th scope="row">After a chat edit, the card floated about 40 px above the player</th><td>Our “Updated from the chat” notice moved the area the card is measured from</td><td>Place the card when that area resizes</td></tr>
</tbody>
</table>
</div>

<h2 id="message-box-safe-area">Why does a fullscreen MCP App hide behind ChatGPT’s message box on a phone?</h2>
<p>Because ChatGPT draws its message box over your frame and tells you how much it covers through the bottom safe-area inset. Ignore the inset and your controls sit under the box; add your own allowance on top, as we did, and they float. The MCP Apps specification describes <code>safeAreaInsets</code> only as “Safe area boundaries in pixels”, and OpenAI’s docs say the composer stays overlaid in fullscreen without giving its size (both checked October 7, 2026). Our 47 px and 127 px are estimates from screenshots.</p>
<p>On October 6 a conversation on a phone showed our player under ChatGPT’s title bar, so we moved the header below the top inset and the player to the bottom, where our CSS also kept 96 px for a message box on top of the bottom inset. A local test with a 34 px inset looked right; ChatGPT’s 127 + 96 did not. Now the panel keeps whichever is larger, and reads ChatGPT’s <code>window.openai.safeArea</code> too:</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">Title bar · 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">Message box · 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">Before</text>
<text class="d-small" x="80" y="349" text-anchor="middle">127 + 96 px kept clear</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">Title bar · 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">Message box · 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">After</text>
<text class="d-small" x="240" y="349" text-anchor="middle">127 px kept clear</text>
</svg>
<figcaption>Our panel on a 402 × 874-point phone, roughly to scale, under ChatGPT’s title bar (about 47 px) and message box (about 127 px). Before the fix it kept 96 px more above the inset; now it keeps the inset, or 96 px if a host reports less.</figcaption>
</figure>

<h2 id="host-context-changed">What changes while a fullscreen MCP App is open on a phone?</h2>
<p>The insets. In our run the bottom edge moved when a draft grew to two lines and when the Thinking bar replaced the message box. The specification lets a host send <code>ui/notifications/host-context-changed</code> whenever a context field changes, with only the changed fields, for the view to merge.</p>
<p>Our player sits on a CSS variable and followed. The selection card is placed by script and stayed behind, so now every context change places it again. Our own notice caused a second drift: “Updated from the chat” shows above the score for 20 seconds and pushes the score area down. The card, measured from that area’s top, slid over the player, and if anything placed it again meanwhile, it floated above the player once the notice went. A <code>ResizeObserver</code> on the area now places the card too, once per animation frame:</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">Why does an MCP App show an old version after leaving fullscreen?</h2>
<p>Because ChatGPT on the phone built a new panel each time it entered or left fullscreen, and gave it the conversation’s original tool result, made before any edits. The specification lets a host tear a view down at any point; a tool result only records the moment its tool ran.</p>
<p>Ours was half snapshot, half live: page images and notes for its own revision, plus a current-revision field read when the panel fetches it. The rebuilt panel drew revision 0, saw that the score was at revision 3 and went read-only. Its check for chat edits compared the server’s revision with that already-current field, so it never caught up. Now a panel that the host hands a result the score has moved past reads the current revision once, unless the result holds a suggested change awaiting approval, which is meant to differ.</p>

<h2 id="test-without-a-phone">How do you test ChatGPT’s phone behavior without a phone?</h2>
<p>Imitate the real host in a replica, and prove each test fails without its fix. We use sunpeak, which replicates ChatGPT and Claude for Playwright tests. Its mobile ChatGPT shell (version 0.20.91) draws a title bar and message box and reports the box as a 92 px inset. The three tests we wrote for these bugs, on a 402 × 874 touch screen, add the rest. Through the shell’s sandbox frame, which is the panel’s parent as ChatGPT’s frame is, they send new insets of 127, 160, 70 and 127 px, and after an edit they reload the panel and hand it the original tool result. Another test edits the score outside the panel, as the chat does. The player must end 0–24 px above the larger of the inset and 96 px, the card 0–16 px above the player.</p>
<figure class="flow-figure">
<ol class="flow-steps">
<li><b>Run the real host</b><span>ChatGPT’s mobile site in a Simulator</span></li>
<li><b>Fix one cause</b><span>Deploy, refresh the app’s tools, look again</span></li>
<li><b>Imitate the host</b><span>Send its messages from the replica’s frame</span></li>
<li><b>Undo the fix</b><span>The new test must fail without it</span></li>
</ol>
<figcaption>How a finding on the phone becomes a local test.</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Replica tests with each fix undone" tabindex="0">
<table>
<caption>Each fix undone, one at a time, October 7, 2026</caption>
<thead><tr><th scope="col">Fix undone</th><th scope="col">What failed</th></tr></thead>
<tbody>
<tr><th scope="row">The inset or 96 px, never both</th><td>Message-box test: “player close to the message box”</td></tr>
<tr><th scope="row">Card placed on a context change</th><td>Message-box test: “card clear of the player”</td></tr>
<tr><th scope="row">Current version for a rebuilt panel</th><td>Rebuild test: undo stayed disabled</td></tr>
<tr><th scope="row">Card placed when the area resizes</th><td>Notice test: “card docked on the player”</td></tr>
</tbody>
</table>
</div>
<p>A run of those three took 59.9 seconds. A fourth, added the same day, checks the phone editor’s one-row header and player, and our contributor rules now require the phone tests after any change to the fullscreen layout, insets, card or result handling. The phone run also caught ChatGPT turning a selected B4 into C4, a seventh down, for “Change this note to C”; a nearest-octave rule in the tool’s description took a local test with a real model from 2 of 3 passing runs to 6 of 6.</p>

<h2 id="not-verified">What we haven’t verified</h2>
<p>Our phone checks ran in the iOS Simulator and ChatGPT’s mobile website: no physical phone, no native app. Still open:</p>
<ul>
<li>the exact insets ChatGPT sends, and when; ours are estimates the replicas reuse;</li>
<li>Safari’s expanded toolbar, which half hides the message box and the player’s lower edge;</li>
<li>a page scroll of about 156 points Safari left after the keyboard closed, which a cross-origin panel can’t undo;</li>
<li>an opaque band up to about 777 points when fullscreen opened while ChatGPT was still answering;</li>
<li>ChatGPT’s reply sheet and its own iframe teardown, which the replicas don’t imitate;</li>
<li>ChatGPT on a computer, where fullscreen is a side panel with the message box outside it; we didn’t read its insets.</li>
</ul>

<h2 id="mcp-app-phone-checklist">A checklist for MCP Apps on phones</h2>
<ul>
<li>Keep both insets clear; if you also keep a fallback for a message box, use the larger, never the sum.</li>
<li>Merge each <code>host-context-changed</code> notification, and place again anything positioned by script, once per frame.</li>
<li>Observe your own containers for notices that move them.</li>
<li>Expect a rebuild from an old tool result; compare its version with the server’s on load.</li>
<li>Give your local test host the insets measured on the real one.</li>
<li>Treat the UI resource URI as a cache key, as OpenAI’s docs advise, and after publishing a new one refresh the app’s tools in ChatGPT (plugin settings, Manage app, Refresh tools; checked October 7, 2026).</li>
<li>Turn each real-host finding into a replica test that fails without its fix.</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx">SEP-1865: MCP Apps: Interactive User Interfaces for MCP (stable, 2026-01-26)</a> — Model Context Protocol</li>
<li><a href="https://developers.openai.com/plugins/build/chatgpt-ui">Add UI to your MCP server</a> — OpenAI</li>
<li><a href="https://developers.openai.com/plugins/concepts/ui-guidelines">UI guidelines</a> — OpenAI</li>
<li><a href="https://developers.openai.com/plugins/reference">Reference</a> (the <code>window.openai</code> component bridge) — OpenAI</li>
<li><a href="https://sunpeak.ai/docs/mcp-testing">MCP Testing Framework</a> — sunpeak</li>
</ol>
</section><h2>Questions and answers</h2><h3>Does ChatGPT count its message box in safeAreaInsets on a phone?</h3><p>In our run, yes. On ChatGPT’s mobile website (iPhone 17 Simulator, October 7, 2026) the bottom inset was about 127 px and covered the message box, and the top inset, about 47 px, covered the title bar. The MCP Apps specification doesn’t say what an inset contains, so keep the reported space clear and don’t add an allowance of your own on top of it.</p><h3>Does ChatGPT change the insets while a fullscreen app is open?</h3><p>Yes, in our run: a two-line draft and ChatGPT’s Thinking bar both moved the bottom edge. Apply new insets whenever <code>ui/notifications/host-context-changed</code> arrives, merging the fields it carries into what you have, and place again anything you position with script.</p><h3>Why does my MCP App show old data after switching to fullscreen on a phone?</h3><p>ChatGPT on the phone built a new panel from the conversation’s original tool result each time it entered or left fullscreen, so ours showed the score as it was before later edits. When the view loads, compare the result’s version with the server’s and read the current state once if the result is behind.</p><h3>Can I test ChatGPT’s phone layout without a phone?</h3><p>Partly. We replay what the phone showed in sunpeak’s mobile ChatGPT shell with Playwright: moving insets, a rebuilt panel and an edit from the chat, and each test fails with its fix undone. The values ChatGPT really sends, Safari’s toolbar, the keyboard, ChatGPT’s reply sheet and the native app still need a real phone.</p>]]></content:encoded>
    </item>
    <item>
      <title>Autoscaling workers from one Postgres queue on Railway</title>
      <link>https://scorestarling.com/blog/autoscale-workers-postgres-queue</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/autoscale-workers-postgres-queue</guid>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Engineering</category>
      <description>How we moved transcription onto 1–4 Railway workers that share one Postgres jobs table: the claim, the 120 s lease, queue-depth scaling and an API bug.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>On October 7, 2026 we split ScoreStarling’s one Railway service in two: the site only serves requests, and one to four worker replicas run the transcriptions. The queue stayed a table in our PostgreSQL database: a worker claims the oldest job under a transaction-level advisory lock and reports every 15 seconds, and another worker takes back any job that goes silent for two minutes. Railway doesn’t add replicas by itself, so the leading worker sets the count from the queue through Railway’s API, after a first version that asked for a field the API doesn’t have.</p>
</section>

<h2 id="why-separate-workers">Why move transcription off the web server?</h2>
<p>Because the web server was also the only transcription worker, and it could grow only by getting a bigger container. Until October 7, one Railway replica with 2 vCPUs and 8 GB served the site, the API and the MCP endpoint, and ran every transcription, one at a time. Only the holder of a session-level advisory lock worked the queue, so a second replica would have added no transcription capacity.</p>
<p>Now the same image runs as two Railway services with a role setting: <code>web</code> never takes a job, and <code>worker</code> has no public address. A job is one transcription (ScoreStarling <a href="https://scorestarling.com/how-it-works">turns recordings into sheet music</a>): 17 to 266 seconds of a worker’s time in the production runs we sampled from October 2 to 7.</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">The site</text>
<text class="d-small" x="160" y="42" text-anchor="middle">pages, uploads, 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">adds jobs</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 jobs table</text>
<text class="d-small" x="160" y="121" text-anchor="middle">the queue, with owners and reports</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">take jobs</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 sets the count</text>
<text class="d-small" x="300" y="226" text-anchor="end">1 to 4 replicas</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">replica count,</text>
<text class="d-small" x="160" y="277">no redeploy</text>
</svg>
<figcaption>The site only adds jobs to a table in PostgreSQL. One to four worker replicas take them, one job each at a time; the dashed ones run only while jobs wait. The worker that leads also sets the replica count through Railway’s API, which starts or stops replicas without a redeploy.</figcaption>
</figure>

<h2 id="postgres-queue-or-broker">Why keep the queue in Postgres instead of a broker?</h2>
<p>Because the job row already holds what a broker would only point to: owner, status, credit reservation, price and files. A broker would be a second store to keep in step, and most brokers deliver at least once, while a band transcription is a paid request to an outside service that must never be sent twice. We would need our own ownership rules anyway.</p>
<p>The load is small: a few dozen waiting jobs at most, each taking seconds to minutes. Railway’s queue guide says a Postgres queue has fewer moving parts when you already run Postgres, at lower throughput than Redis. Queue libraries such as pg-boss would add a second job table. We’ll reconsider at tens of jobs a second.</p>

<h2 id="postgres-queue-multiple-workers">How do several workers share one Postgres queue without taking the same job?</h2>
<p>Each claim is one short transaction under one lock for the whole queue. An idle worker checks without the lock whether anything is queued, and if not, looks again two seconds later:</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>A second worker waits at the lock, and its next query starts after the first has committed. At Read Committed, PostgreSQL’s default, a query sees what was committed before it began, so the job already shows as running. In our checks, six workers on separate connections drained 40 queued jobs, each taken exactly once.</p>
<p>With many workers, <code>SELECT … FOR UPDATE SKIP LOCKED</code> is the usual choice; PostgreSQL’s documentation suggests it for queue-like tables. We kept one lock because uploads, credit changes and recoveries already take it (in the local SQLite version it is <code>BEGIN IMMEDIATE</code>), so claims take turns with them. We haven’t measured that wait. The queue also needs no database session of its own: no <code>LISTEN</code>, which PgBouncer’s transaction pooling doesn’t support, and no session-level lock like the one our old single leader held.</p>

<h2 id="worker-crash-recovery">What happens to a job when its worker stops?</h2>
<figure class="flow-figure">
<ol class="flow-steps">
<li><b>Queued</b><span>The site saves the upload and adds a job row.</span></li>
<li><b>Claimed</b><span>A free worker takes the oldest job under the lock.</span></li>
<li><b>Running</b><span>The worker reports every 15 s, with the stage reached.</span></li>
<li><b>Finished</b><span>The score is saved, and the worker looks for more.</span></li>
</ol>
<figcaption>The life of a job. Each step is a change to the job’s row in PostgreSQL, which is also where the site reads its progress.</figcaption>
</figure>
<p>It is recovered, and paid work is never sent twice. A worker stopped on purpose, by a deploy for example, puts its free job back in the queue itself. A worker that crashes just goes silent. Every 15 seconds a worker writes the time and its stage to its job’s row, and every 30 seconds each worker looks, under the same lock, for running jobs whose 120-second lease has run out without a report. A free transcription goes back to the queue once; a second interruption fails it. A band transcription resumes from its saved request, or waits for review without one. A worker that was only slow, and finds its job given away, kills its process and writes nothing.</p>
<p>Once-only work, such as the controller, runs on the leader: the lowest ID among workers that reported to a <code>workers</code> table in the last 45 seconds, which each does every 15. Nothing is held, so a dead leader is replaced within 45 seconds.</p>

<h2 id="queue-depth-autoscaling">Why scale workers on queue depth instead of CPU?</h2>
<p>Because a worker runs one job at a time, the workers needed equal the jobs queued or running. A busy worker’s CPU says it is busy, not whether one job or twenty are waiting, and Railway’s autoscaling guide names queue depth as the signal for workers too. Railway grows a container up to its CPU and memory limits by itself, but a replica count stays where you set it; the guide’s controller scales up at once and down a step at a time.</p>
<p>Ours cuts back differently. The API takes a count and Railway picks which replica to stop, so we cut back only when nothing is queued or running and no worker is busy, then straight to one.</p>
<div class="table-scroll" role="region" aria-label="How the worker controller decides" tabindex="0">
<table>
<caption>How the controller decides, as set on October 7, 2026</caption>
<thead><tr><th scope="col">Rule</th><th scope="col" class="num">Value</th><th scope="col">Why</th></tr></thead>
<tbody>
<tr><th scope="row">Time between looks at the queue</th><td class="num">30 s</td><td>Only the leader looks, so each decision is made once.</td></tr>
<tr><th scope="row">Workers kept, and the most allowed</th><td class="num">1 to 4</td><td>One is ready for the next job; the cap bounds the bill.</td></tr>
<tr><th scope="row">Wait before a job asks for a worker while one looks free</th><td class="num">30 s</td><td>A free worker normally takes it within 2 s; with none free, the next look asks.</td></tr>
<tr><th scope="row">Time between two increases</th><td class="num">90 s</td><td>New workers get time to start before the next ask.</td></tr>
<tr><th scope="row">Time a new worker is given to appear</th><td class="num">300 s</td><td>Until then the controller doesn’t ask again.</td></tr>
<tr><th scope="row">Quiet time before cutting back to one</th><td class="num">600 s</td><td>With no job and no busy worker, the replica Railway stops is idle.</td></tr>
</tbody>
</table>
</div>
<p>When jobs wait, the controller asks for one worker per queued or running job, at least one more than it has and at most four.</p>

<h2 id="railway-api-failure">What broke in production?</h2>
<p>The controller’s first call to Railway’s real API; our tests had used a stand-in that answered any query. Five minutes after the change that switched the controller on was merged, every look at the queue failed. The workers kept taking jobs, but the log and Sentry said only <code>RailwayError</code>: our logs leave out exception messages.</p>
<p>The same query sent without a token reproduced it. Railway validated it against its schema first and answered HTTP 400, <code>GRAPHQL_VALIDATION_FAILED</code>. Our service is placed by region, so the write sets <code>multiRegionConfig</code>, which the update input accepts, but our read asked a service instance for that field, and it has none. The fix reads the count from the environment’s config, which our Railway config file and <code>railway scale</code> also change.</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>The log now carries Railway’s own message, which never contains the token, since a refused token and a refused query were both HTTP 400. The stand-in refuses the old query. And a new check sends both operations to the live schema with placeholder IDs and no token: GraphQL servers validate before executing, so a wrong field fails validation, while a valid query gets as far as Railway’s “Not Authorized”.</p>
<figure class="timeline-figure">
<ol class="timeline">
<li><time datetime="2026-10-06">Oct 6</time><p>The queue may hold several times more waiting jobs, still behind one worker.</p></li>
<li><time datetime="2026-10-07T04:22Z">Oct 7, 04:22</time><p>Merged: workers share the queue, and a worker service starts beside the site. In a test, two uploads run at once.</p></li>
<li><time datetime="2026-10-07T04:48Z">04:48</time><p>Merged: the site stops taking jobs, and the controller is switched on.</p></li>
<li><time datetime="2026-10-07T04:53Z">04:53</time><p>Every look at the queue fails with <code>RailwayError</code>. Jobs still run.</p></li>
<li><time datetime="2026-10-07T05:07Z">05:07</time><p>Merged: the count is read from the environment’s config.</p></li>
<li><time datetime="2026-10-07T05:12Z">05:12</time><p>The first count read from Railway: one worker, steady.</p></li>
<li><time datetime="2026-10-07T05:17Z">05:17</time><p>Two jobs at once. The controller asks for a second worker, which is up 17 seconds later.</p></li>
<li><time datetime="2026-10-07T05:28Z">05:28</time><p>After 602 seconds with nothing to do, back to one. No errors through 06:28.</p></li>
</ol>
<figcaption>The rollout, from our verification record. Times are UTC.</figcaption>
</figure>
<p>The same day, the leader began saving each look’s outcome for the site’s health page, which warns when Railway refuses the token or no look arrives for two minutes. The site’s own health check no longer covers the workers, so a worker outage now shows in its five-minute service check, and Sentry emails us when a service stays unwell twice in a row.</p>

<h2 id="not-verified-yet">What we haven’t verified, and what it costs</h2>
<ul>
<li>Only free transcriptions have run on the workers; a band transcription there, and its recovery after a lost worker, are untested.</li>
<li>Our test account may have two scores in progress, and we skipped the planned days of dry-run logging, so the controller has met one small burst and an hour of quiet. Three or four workers, the 90-second step and stopping a busy replica haven’t happened in production.</li>
<li>Production doesn’t record when a job is taken, so we can’t say yet how much shorter the wait in line is. In the scale-up test the new worker found nothing to do: the first had already taken the waiting job.</li>
<li>After ten quiet minutes the pool is back to one, so a burst waits up to 30 seconds for the next look, then for a new worker to start: 17 seconds in our one test.</li>
<li>At the maximum, four jobs run and the rest wait in line.</li>
<li>A deploy restarts every worker, returning a running free transcription to the queue once, and each CI run that applies our Railway config resets the count to one until the next look.</li>
</ul>
<p>Railway charges $20 per vCPU and $10 per GB of memory a month, by the minute (checked October 7, 2026). We estimate a worker at about $0.08 an hour while it transcribes at 2 vCPUs and 1.5 GB, and much less while it waits. A hard usage limit on Railway takes every workload offline when reached, so raise such a limit together with the maximum.</p>

<h2 id="worker-autoscaling-checklist">If you build this yourself</h2>
<ul>
<li>Keep the queue with the job’s state unless you need a broker’s routing or throughput.</li>
<li>Claim in one short transaction; with many workers, use <code>SKIP LOCKED</code>.</li>
<li>Keep session state out of the queue: no <code>LISTEN</code>, no session-level locks.</li>
<li>Give each running job an owner and a heartbeat, and make a worker that lost its job stop without writing. Never resend a paid outside request; resume it from its saved ID.</li>
<li>Scale on queued plus running jobs, and cut back only when no worker is busy if the platform picks which replica stops.</li>
<li>Test the platform’s API against its live schema, and log its error messages once you know they hold no secrets.</li>
<li>Give the controller somewhere to report.</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS">13.3. Explicit Locking: 13.3.5 Advisory Locks, PostgreSQL 18 documentation</a> — PostgreSQL Global Development Group</li>
<li><a href="https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS">9.28. System Administration Functions: 9.28.10 Advisory Lock Functions, PostgreSQL 18 documentation</a> — PostgreSQL Global Development Group</li>
<li><a href="https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE">SELECT: The Locking Clause, PostgreSQL 18 documentation</a> — PostgreSQL Global Development Group</li>
<li><a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED">13.2. Transaction Isolation: 13.2.1 Read Committed Isolation Level, PostgreSQL 18 documentation</a> — PostgreSQL Global Development Group</li>
<li><a href="https://www.pgbouncer.org/features.html">PgBouncer features</a> — PgBouncer</li>
<li><a href="https://docs.railway.com/guides/autoscale-horizontally">Autoscale a Service Horizontally Based on Load</a> — Railway</li>
<li><a href="https://docs.railway.com/guides/cron-workers-queues">Choose Between Cron Jobs, Background Workers, and Queues</a> — Railway</li>
<li><a href="https://docs.railway.com/pricing/plans">Pricing Plans</a> — Railway</li>
<li><a href="https://docs.railway.com/pricing/cost-control">Cost Control</a> — Railway</li>
<li><a href="https://spec.graphql.org/October2021/#sec-Validating-Requests">GraphQL Specification, October 2021 Edition: 6.1.1 Validating Requests</a> — GraphQL Foundation</li>
</ol>
</section><h2>Questions and answers</h2><h3>Does Railway autoscale replicas horizontally?</h3><p>Not as of October 7, 2026. Railway gives a container more CPU and memory up to its limits, but a service’s replica count stays where you set it. Its autoscaling guide has you run a controller that reads a load signal and calls the API’s <code>serviceInstanceUpdate</code>.</p><h3>Should a Postgres job queue use SKIP LOCKED or an advisory lock?</h3><p><code>FOR UPDATE SKIP LOCKED</code> lets many workers claim different rows at once, and PostgreSQL’s documentation suggests it for queue-like tables. A single transaction-level advisory lock makes claims take turns, which is simpler for a few workers, especially when the same lock already guards your other writes.</p><h3>Why does Railway’s API answer GRAPHQL_VALIDATION_FAILED for multiRegionConfig?</h3><p>In our case, because we asked a service instance to return <code>multiRegionConfig</code>, which only the update input has. Read the count from <code>environment(id) { config }</code> at <code>services.&lt;service id&gt;.deploy.multiRegionConfig</code>, and write it with <code>serviceInstanceUpdate</code>.</p><h3>What happens to a running job when a worker replica is removed?</h3><p>Our controller removes replicas only while no worker is busy. A busy worker stopped anyway, by a deploy for example, returns a free transcription to the queue once; if it vanishes, another worker takes the job back after 120 seconds without a report. Paid requests are never sent again.</p><h3>How much does an extra worker cost on Railway?</h3><p>Railway bills $20 per vCPU and $10 per GB of memory a month, by the minute (checked October 7, 2026). We estimate about $0.08 an hour for a worker transcribing at 2 vCPUs and 1.5 GB, and much less while it waits. Our cap is four workers.</p>]]></content:encoded>
    </item>
    <item>
      <title>Can ChatGPT or Claude turn a recording into sheet music?</title>
      <link>https://scorestarling.com/blog/chatgpt-claude-sheet-music</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/chatgpt-claude-sheet-music</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Engineering</category>
      <description>Not on their own. Connected to a transcription tool over MCP, Claude and ChatGPT can turn a recording into an editable score. Setup, a sample session, limits.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>Not on their own. A chat assistant can talk about music and write notes out as text, but it won’t give you a reliable, editable score of a recording you made, and neither company’s documentation that we checked on October 3, 2026 describes turning audio into notation. Connect a music transcription tool through MCP and both can do it: the tool detects the notes and engraves the score, and the assistant turns your requests into tool calls. That is how ScoreStarling works with Claude and ChatGPT; it is waiting for review in both apps’ directories, and you can add it by its address today.</p>
</section>

<h2 id="what-happens-if-you-ask">What happens if you just ask ChatGPT or Claude?</h2>
<p>You get words about the music, or notes the assistant writes itself, rather than a transcription of your file. Claude’s help center lists documents (PDF, DOCX, CSV, TXT and others) and images (JPEG, PNG, GIF, WebP) as the files you can upload, with no audio format among them. OpenAI’s file transcription guide is about turning recorded speech into a transcript and doesn’t mention music or notation. We checked both on October 3, 2026.</p>
<p>What assistants can do is write music as text. ABC notation, for example, is plain text that software can draw as a staff, and the official MCP Apps example, Sheet Music Server, takes the ABC an assistant writes and renders it as a playable score. That helps when you sketch an idea. It isn’t a transcription, because nothing measured the pitches and timing in your recording, so you would have to check every note by ear.</p>
<p>A usable transcription is a chain of jobs: find each note’s pitch, start and length in the audio; fit the notes to a beat and a meter; choose a key signature, spell the accidentals, place rests and split voices; then engrave the page and save it in a format notation software opens. Each step has specialized software. Spotify’s open-source Basic Pitch, which we run on our server for one instrument or voice, does the first one, and its authors say it works best on one instrument at a time. MuseScore Studio 4.7.5’s MIDI importer does the middle steps for us, and Verovio engraves the page with the Leipzig music font.</p>

<h2 id="what-mcp-changes">What does connecting a tool over MCP change?</h2>
<p>It lets the assistant hand the music to software built for it while you keep asking in plain words. The Model Context Protocol is, in its documentation’s words, “an open-source standard for connecting AI applications to external systems.” A service runs an MCP server that offers tools, and an assistant that connects to it can call those tools in the middle of a conversation. In Claude, a server you add yourself is a custom connector; in ChatGPT, it becomes an app you create in developer mode.</p>
<p>Connecting ScoreStarling opens our sign-in page in your browser. You allow access, and the assistant can then work with your projects and nobody else’s. From then on the jobs split like this:</p>
<div class="table-scroll" role="region" aria-label="What the assistant does and what ScoreStarling’s tools do" tabindex="0">
<table>
<caption>Who does what in a connected session</caption>
<thead>
<tr><th scope="col">Step</th><th scope="col">The assistant</th><th scope="col">ScoreStarling’s tools</th></tr>
</thead>
<tbody>
<tr><th scope="row">Your request</th><td>Works out what “the last note of bar 1, an octave higher” means and picks a tool</td><td>Return the score’s notes, bars and versions so the request can be pinned to exact notes</td></tr>
<tr><th scope="row">The recording</th><td>Passes your attachment on (ChatGPT) or uploads it from its code sandbox (Claude)</td><td>Issue a one-time upload link that expires after 10 minutes</td></tr>
<tr><th scope="row">Transcription</th><td>Checks the job’s status instead of uploading again</td><td>Detect the notes (Basic Pitch for one instrument or voice), write the score and engrave it with Verovio</td></tr>
<tr><th scope="row">Changes</th><td>Turns your words into one exact edit, and offers its own suggestions as previews</td><td>Apply edits with undo and saved versions; render previews with audio</td></tr>
<tr><th scope="row">Export</th><td>Hands you the files in the chat, or points you to the panel’s Download menu</td><td>Write PDF, MusicXML, MIDI, MP3 or WAV; links expire after an hour</td></tr>
</tbody>
</table>
</div>
<p>The tools and the <a href="https://scorestarling.com/app">web workspace</a> work on the same saved score, so a change made from chat is there when you open the workspace.</p>

<h2 id="a-session-step-by-step">What does a session look like, from recording to PDF?</h2>
<p>A handful of plain requests take you from the recording to the files: transcribe, fix, preview and export, with listening in between. The steps below use the example prompts from our <a href="https://scorestarling.com/mcp#ask">connection guide</a>. In ChatGPT we have run most of these steps in real conversations, though not this exact one: an attached recording transcribed on October 5, 2026, and on October 6 a transcription from a link, the score panel, playback, a preview, apply, undo and downloads. In Claude, a score written from notes opened in its panel and played on October 6; uploading a recording from Claude is still being tested.</p>
<ol>
<li>Attach the recording and ask: “Use ScoreStarling to turn this voice memo into sheet music. It’s about 84 BPM.” Include the tempo if you know it. Without one, ScoreStarling estimates it from the recording, which can land on a related tempo for swung or rubato playing.</li>
<li>The file travels differently in each app. ChatGPT hands the attachment to the transcription tool. Claude reads it in its code sandbox, asks ScoreStarling for a one-time upload link and sends the file there, which is why the sandbox needs network access. If the upload can’t happen in your app, the assistant can open ScoreStarling’s upload panel, or you can upload the take in the web workspace.</li>
<li>Our server transcribes the take at the tempo you gave or one it detects. A voice or one instrument starts in 4/4, which you can change; solo piano also works out the meter and bar lines. The assistant opens it in the in-chat panel where your app can show one. The same project is in the workspace, which plays the transcription and your recording in sync, with a crossfade and looping, so you can hear where they part.</li>
<li>Correct what you hear: “In my ScoreStarling score, the last note of bar 1 should be an octave higher.” The assistant reads the score to find that note and applies the exact edit. Edits you spell out apply at once and can be undone.</li>
<li>Try a bigger change before you commit: “Transpose my ScoreStarling score up a whole step, and let me hear a preview first.” The tool renders the transposed score and its audio as a preview. Nothing changes until you accept it, and you can discard it instead.</li>
<li>Export: “Export my ScoreStarling score as PDF and MusicXML.” You get the two files in the chat or from the panel’s Download menu; their links expire after an hour. The PDF is ready to print, with a small footer line until the score is unlocked, and MusicXML opens in MuseScore, Dorico, Sibelius or Finale for more editing. MusicXML needs the score unlocked, 15 credits once, and the assistant asks before it spends them.</li>
</ol>
<p>Two more prompts from the guide are worth knowing. “Play my ScoreStarling melody back on a flute” changes the General MIDI sound used for playback and audio exports. “Is anything still running in my ScoreStarling account?” checks your jobs before you start another, since a free account runs one at a time.</p>

<h2 id="connect-claude">How do I connect it in Claude?</h2>
<p>Add ScoreStarling as a custom connector. Anthropic’s help center says custom connectors work on the Free, Pro, Max, Team and Enterprise plans, with Free accounts limited to one (checked October 3, 2026).</p>
<ol>
<li>Open Customize → Connectors, select + Add, then Add custom connector. Name it ScoreStarling and enter <code>https://mcp.scorestarling.com/mcp</code>.</li>
<li>Under Authentication, choose Sign in now. Under OAuth client, choose Register automatically. Claude recommends its published identity instead, but that option relies on client ID metadata documents, which our sign-in server doesn’t support yet.</li>
<li>Sign in to ScoreStarling. In a chat, select + in the message box, open Connectors and switch ScoreStarling on.</li>
</ol>
<p>On Team and Enterprise plans, an owner first adds the connector under Organization settings → Connectors, and members then select Connect. To transcribe a recording you attach, Claude’s code sandbox has to be allowed to reach ScoreStarling; <a href="https://scorestarling.com/mcp#claude-upload">the guide shows the setting</a>. Uploading from Claude is still being tested, so the upload panel and the workspace remain the fallback. The <a href="https://scorestarling.com/mcp#claude">Claude steps</a> also offer a button that fills in the form for you; Anthropic doesn’t document that shortcut, so the manual path sits beside it.</p>
<p>In Claude Code it is one command, then <code>/mcp</code> in a session to sign in:</p>
<pre><code>claude mcp add --transport http --scope user scorestarling https://mcp.scorestarling.com/mcp</code></pre>

<h2 id="connect-chatgpt">How do I connect it in ChatGPT?</h2>
<p>Use developer mode, which OpenAI offers to Plus, Pro, Business, Enterprise and Education accounts on the web (checked October 3, 2026).</p>
<ol>
<li>Turn on Developer mode in Settings → Security and login. In Business, Enterprise and Edu workspaces, an admin may have to allow it first.</li>
<li>Open Plugins, select +, name the app ScoreStarling and paste <code>https://mcp.scorestarling.com/mcp</code>. Choose OAuth with dynamic client registration; there is no API key.</li>
<li>Sign in to ScoreStarling. The app appears under Drafts; pick it from the Developer mode tool in the message box.</li>
</ol>
<p>OpenAI’s guide flags developer mode as elevated risk and says write actions ask for confirmation by default, so expect ChatGPT to check with you before a tool changes something. ScoreStarling is waiting for review in the ChatGPT app directory, so a search there finds nothing yet. The <a href="https://scorestarling.com/mcp#chatgpt">ChatGPT steps</a> cover what to check when Developer mode or the + button is missing.</p>

<h2 id="what-doesnt-work-yet">What doesn’t work yet?</h2>
<p>Some of it is still being tested, and some is out of scope. Product limits checked October 7, 2026; client menus were checked October 3.</p>
<ul>
<li>ScoreStarling is free to start. One instrument or voice, solo piano, imports, editing and PDF, MP3 and WAV downloads are free, and every account gets 100 free credits a month in 2026, 50 from January 2027. A band uses credits, and unlocking a score’s MusicXML and MIDI costs 15 (see <a href="https://scorestarling.com/pricing">pricing</a>). In a chat, ScoreStarling only uses the credits you already have and never sells anything. A free account can have one score in progress at a time, and Plus five.</li>
<li>Neither ChatGPT nor Claude lists it in its directory yet: we submitted it to both on October 6, 2026, and it is waiting for review. Until it is listed, ChatGPT needs developer mode.</li>
<li>The in-chat score panel has opened in ChatGPT and Claude in our tests, and ChatGPT has passed an attached recording to ScoreStarling; uploading from Claude and other clients is still being tested. The web workspace is the fallback for every step.</li>
<li>Uploads are limited to 100 MiB, and every choice transcribes the first five minutes of a recording. Upload audio or video, paste a link, or record in the browser. Pick the choice that matches what’s playing; see <a href="https://scorestarling.com/how-it-works#engines">the guide to the choices</a>.</li>
<li>The tempo is detected unless you give one. A voice or one instrument starts in 4/4, which you can change; solo piano works out 4/4, 3/4, 6/8 or 12/8, the bar lines and a pickup; a band score’s meter is found from the playing or chosen before it starts. Every estimate needs checking. TAB doesn’t write bends, slides or hammer-ons, and imported notation and numbered views have their own <a href="https://scorestarling.com/how-it-works#specs">limits</a>.</li>
<li>Remote MCP now also reads sheet music and score files and creates scores from ABC or MusicXML text. An assistant can write that notation itself; composition comes from the assistant. ScoreStarling’s plugin, with its two skills, is waiting for directory review too. <a href="https://scorestarling.com/mcp#webmcp">WebMCP Beta</a> is a separate route for agents in an open browser workspace.</li>
<li>It doesn’t generate new recordings. MP3 and WAV exports are General MIDI renderings of the score, not stems separated from your recording.</li>
<li>Every transcription is a first draft. Our October 2 comparison measured guitar recordings and one band excerpt, not singing or solo piano, and <a href="https://scorestarling.com/blog/ai-music-transcription-accuracy">the results are published</a>. Listen and correct before you share a score.</li>
</ul>

<h2 id="other-ways">What are the other ways to do it?</h2>
<p>Other services connect music tools to assistants too. We read each one’s own pages on October 3, 2026.</p>
<ul>
<li>Mirelo runs a hosted MCP server at <code>https://mcp.mirelo.ai/mcp</code>. You sign in with a Mirelo account and need no API key, and its guide covers Claude, ChatGPT in developer mode, Cursor, Claude Code and Codex. Its July 30, 2026 launch post says Audio-to-MIDI is available for existing recordings, and the Audio-to-MIDI skill in its plugin repository returns MIDI and MusicXML downloads and detects the tempo unless you give one. Most of its other tools make and edit sound effects. Mirelo is also our band partner inside ScoreStarling, so we are not neutral about it.</li>
<li>Melogen’s MCP server, for ChatGPT in developer mode with OAuth sign-in, reads printed sheet music from a PDF, PNG or JPG into MIDI and MusicXML, and analyzes a song’s tempo, key and structure. Its public plugin has no tool that turns a recording into a score.</li>
<li>If you would rather run everything on your own computer, audio2score-mcp is an open-source local server that chains Basic Pitch and music21 into <code>.mid</code> and <code>.musicxml</code> files, with setup notes for Claude Code. It needs no account, and its last commit was on August 20, 2026.</li>
<li>Without an assistant, upload the take in ScoreStarling’s web workspace, which makes the same score.</li>
</ul>
<p>Our <a href="https://scorestarling.com/blog/sheet-music-mcp-servers">map of sheet music MCP servers</a> compares 26 of them side by side, including servers that control MuseScore or analyze scores with music21.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://modelcontextprotocol.io/docs/getting-started/intro">What is the Model Context Protocol (MCP)?</a> — Model Context Protocol documentation</li>
<li><a href="https://support.claude.com/en/articles/8241126-upload-files-to-claude">Upload files to Claude</a> — Claude Help Center</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 Help Center</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 Help Center</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 documentation</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 blog, July 30, 2026</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>Questions and answers</h2><h3>Do I need a paid plan to use a sheet music connector in ChatGPT or Claude?</h3><p>In ChatGPT, yes: developer mode is offered on Plus, Pro, Business, Enterprise and Education accounts, on the web. Claude allows custom connectors on every plan, and the Free plan is limited to one. Both checked October 3, 2026. On ScoreStarling, one instrument or voice, solo piano and editing are free; a band uses credits, and so does unlocking a score’s MusicXML and MIDI. Every account gets 100 free credits a month in 2026 and 50 from January 2027 (checked October 7, 2026).</p><h3>Is ScoreStarling in the ChatGPT or Claude app directory?</h3><p>Not yet. We submitted it to both directories on October 6, 2026, and it is waiting for review. Until then, add ScoreStarling by its address, <code>https://mcp.scorestarling.com/mcp</code>, as the <a href="https://scorestarling.com/mcp">connection guide</a> shows. Anyone can sign in with Google or email.</p><h3>Can it transcribe a full band recording?</h3><p>Yes: choose a band, which uses credits, 40 a minute, with the price shown before it starts. Our band partner Mirelo transcribes it, and like any transcription the result needs checking. One instrument or voice and solo piano are free. Every choice covers the first five minutes of a recording, with a 100 MiB upload limit.</p><h3>Will the assistant change my score without asking?</h3><p>Edits you spell out, such as “make the last note of bar 1 an octave higher”, apply at once and can be undone. Changes the assistant suggests, and any you ask to hear first, arrive as previews; nothing changes until you accept one.</p><h3>Can an assistant write guitar tab from my recording?</h3><p>Yes, since October 8, 2026. Ask for TAB and ScoreStarling shows a guitar, bass or ukulele part with TAB under the staff, or TAB alone, in the tuning and capo you name; the assistant can move a note to another string and paste the TAB as text into its answer. Bends, slides and hammer-ons aren’t written. A local open-source MCP server, fingerstyle-tab-mcp, also turns guitar recordings into text tablature; our <a href="https://scorestarling.com/blog/sheet-music-mcp-servers">map of sheet music MCP servers</a> lists it with the others.</p>]]></content:encoded>
    </item>
    <item>
      <title>How accurate is AI music transcription? We measured it</title>
      <link>https://scorestarling.com/blog/ai-music-transcription-accuracy</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/ai-music-transcription-accuracy</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Research</category>
      <description>We scored Basic Pitch and Mirelo on 12 public guitar recordings and a 60-second band excerpt: onset F1 in plain words, the results, and the caveats.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>Good enough for a first draft, not for a finished score. In our October 2, 2026 test on 12 public guitar recordings, Spotify’s open-source Basic Pitch reached an onset F1 of 0.744 and Mirelo’s Audio-to-MIDI 0.471, where 1.0 would mean every note found at the right pitch and time with nothing extra. Mirelo returned no notes for three of the six solo recordings, but it led on a 60-second band excerpt, 0.672 to 0.520. Note lengths were far less reliable than note starts, and none of these numbers says whether the written score is readable.</p>
</section>

<h2 id="what-onset-f1-means">What does onset F1 measure?</h2>
<p>Onset F1 scores a transcription note by note against a reference transcription, and it punishes missed notes and extra notes alike. A transcribed note counts as a match when its pitch is within 50 cents (a quarter tone) of a reference note and it starts within 50 milliseconds of it. Each reference note can be matched only once.</p>
<p>Two shares come out of the matching. Precision is the share of the model’s notes that match; recall is the share of reference notes the model found. F1 is their harmonic mean, so it stays low when either one is low. Say a passage has 100 notes. A model that writes 100 notes, 75 of them right, scores 0.75. A model that finds 90 of the 100 but writes 150 notes to do it has a recall of 0.9, a precision of 0.6 and an F1 of 0.72: the 60 extra notes cost it.</p>
<p>Onset+offset F1 adds a condition: each note must also end within 20% of the reference note’s length, or within 50 ms if that is longer. Spotify’s Basic Pitch paper uses onset F1 as its main measure because note ends are less objective; it names reverb, the sustain pedal and the way annotations are made. We report both. Neither one checks which instrument a note was given, the key signature or how the rhythm would be written.</p>

<h2 id="how-we-tested">How we tested</h2>
<p>We used GuitarSet 1.1.0 (Xi et al., ISMIR 2018), a public dataset of 360 guitar excerpts of about 30 seconds: six guitarists play the same 30 lead sheets in five styles, first as an accompaniment (“comp”) and then as a solo over it. It was recorded with a hexaphonic pickup, which gives each string its own signal and let the authors largely automate the note annotations.</p>
<p>We took 12 full recordings, one solo and one comp per guitarist, using the mono microphone audio: 433 seconds in all. The choice was fixed before any model ran. For each guitarist and mode we took the file whose SHA-256 hash of “scorestarling-v1:” plus its name sorts first, after excluding three recordings with known annotation errors (<a href="https://github.com/marl/GuitarSet/issues">GuitarSet issues #4 and #5</a>: a duplicated note and wrong timings). The <a href="#per-recording">table of results by recording</a> below names the 12 it picked.</p>
<p>Scoring used <a href="https://mir-eval.readthedocs.io/latest/api/transcription.html">mir_eval</a> 0.8.2 with its default tolerances, as described above, averaged per recording so that each one counts equally. Nothing was tuned: no timing offsets, no filtering, no alignment shift. Basic Pitch 0.4.0 ran on a Mac, and two full runs gave byte-identical output. Mirelo’s Audio-to-MIDI API, which reported its model as a2m-1.1, ran the same 12 recordings on October 2, 2026, without the optional instrument list its API has accepted since August 6, 2026, according to <a href="https://mirelo.ai/changelog">Mirelo’s changelog</a>.</p>
<p>Four limits apply to everything below:</p>
<ul>
<li>Basic Pitch’s paper lists GuitarSet and Slakh among its training data, so both tests favor it. We can’t tell whether our 12 GuitarSet recordings were in its training split; the band excerpt comes from Slakh’s test split.</li>
<li>Twelve guitar recordings and one 60-second band excerpt make a diagnostic sample, not a benchmark. Voice and recorded band performances are unmeasured, and piano is measured only on synthesized audio, with another model (see the <a href="#faq">questions at the end</a>).</li>
<li>Mirelo ran the guitar recordings once, on October 2. We haven’t retried the three where it returned nothing, for example by telling it to expect acoustic guitar. Sent again on October 5, the band excerpt came back with the same notes.</li>
<li>None of these numbers rates the written score. A score-level measure such as MV2H, which also checks meter, voices and note values, is still to be done.</li>
</ul>
<p class="note">ScoreStarling uses Basic Pitch for one instrument or voice, and Mirelo, our band partner, for a band, so we are not neutral observers. That is why the method, the sample and its limits are spelled out here.</p>

<h2 id="basic-pitch-vs-mirelo">Basic Pitch vs Mirelo on guitar recordings</h2>
<p>Basic Pitch came out ahead: far ahead on solo lines, narrowly on accompaniment.</p>
<div class="table-scroll" role="region" aria-label="Onset F1 for Basic Pitch and Mirelo on 12 GuitarSet recordings" tabindex="0">
<table>
<caption>GuitarSet, 12 recordings, macro average, October 2, 2026</caption>
<thead>
<tr><th scope="col">Recordings</th><th scope="col" class="num">Basic Pitch onset F1</th><th scope="col" class="num">Mirelo onset F1</th><th scope="col" class="num">Basic Pitch onset+offset F1</th><th scope="col" class="num">Mirelo onset+offset F1</th></tr>
</thead>
<tbody>
<tr><th scope="row">Solo (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">Accompaniment (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">All (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">Update, October 4, 2026: we ran the same 12 recordings again through ScoreStarling’s one-instrument option after two changes to it: a take steadily sharp or flat is retuned to A440 first, and a melody is written one note at a time. It scored 0.846 on the solos, 0.679 on the accompaniments and 0.763 overall, or 0.522 with note ends. The table keeps the October 2 results of Basic Pitch on its own.</p>
<p>Mirelo returned no notes at all for three of the six solo recordings. They count as zero in its solo average, because an empty result is what a user would have received, and we have not rerun them. Where it did return notes, the picture was mixed. On the first recording, 00_BN3-154-E_solo, the two engines were level on note starts (0.809 for Basic Pitch, 0.814 for Mirelo), but Mirelo’s note ends were further off (onset+offset F1 0.320 against 0.524), and it put the 154 BPM piece at about 76.9 BPM, half the tempo. On accompaniment, Mirelo was clearly better on two of the six recordings. Across all 12, its onset+offset F1 was 0.224.</p>
<div class="table-scroll" id="per-recording" role="region" aria-label="Onset F1 per recording for Basic Pitch and Mirelo" tabindex="0">
<table>
<caption>Onset F1 by recording, GuitarSet, October 2, 2026</caption>
<thead>
<tr><th scope="col">Recording</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 (no notes)</td></tr>
<tr><th scope="row">04_SS3-84-Bb_solo</th><td class="num">0.821</td><td class="num">0 (no notes)</td></tr>
<tr><th scope="row">05_SS1-68-E_solo</th><td class="num">0.768</td><td class="num">0 (no notes)</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’s 0.744 sits close to what its authors report on their own 72-recording GuitarSet test split: 0.79 onset F1, and 0.56 with note ends. The engines also differed in speed. Basic Pitch got through the 433 seconds of audio in 54 seconds on a Mac, which is not a production timing; on the first recording it took 6.1 seconds and Mirelo 37.</p>

<h2 id="full-band-recording">What happens with a full band?</h2>
<p>A mix is harder, and there Mirelo led. We took the first 60 seconds of Track01881 from the test split of <a href="https://zenodo.org/records/4599666">Slakh2100</a>, a dataset of 2,100 songs rendered from MIDI with sample-based virtual instruments, so the reference notes are exact. The excerpt has steel-string and jazz guitar, piano, bass, vibraphone and drums, all playing within that minute. Pooled over every pitched instrument, ignoring which instrument a note was assigned to, onset F1 was 0.520 for Basic Pitch and 0.672 for Mirelo, scored on the MIDI file each engine returns.</p>
<p>Basic Pitch writes one combined part and no drums. Mirelo writes a part per instrument, so only Mirelo can be scored by instrument, grouped here into General MIDI instrument classes:</p>
<figure class="bars-figure">
<div class="bars bars-wide" aria-hidden="true">
<p class="bars-title">Mirelo’s onset F1 by instrument <span>one 60-second band excerpt, 0 to 1</span></p>
<div class="bar-row"><span>Piano</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>Drums</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>Guitars</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>Bass</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>Vibraphone</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>Piano and drums came through best; the vibraphone was mostly missed. The table below has the reference note counts.</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Mirelo onset F1 per instrument on a Slakh2100 excerpt" tabindex="0">
<table>
<caption>Mirelo per instrument class, Slakh2100 Track01881, first 60 seconds</caption>
<thead>
<tr><th scope="col">Instrument class (reference notes)</th><th scope="col" class="num">Onset F1</th></tr>
</thead>
<tbody>
<tr><th scope="row">Piano (178)</th><td class="num">0.827</td></tr>
<tr><th scope="row">Guitar: steel-string and jazz (334)</th><td class="num">0.681</td></tr>
<tr><th scope="row">Bass (127)</th><td class="num">0.455</td></tr>
<tr><th scope="row">Vibraphone (64)</th><td class="num">0.137</td></tr>
<tr><th scope="row">Drum strokes (423)</th><td class="num">0.698</td></tr>
</tbody>
</table>
</div>
<p>Telling the two guitars apart was harder. Scored part by part from Mirelo’s note list on October 2, the steel-string guitar reached 0.364 and the jazz guitar 0.148; the MIDI file starts its notes about 20 ms earlier than that list, so its figures differ slightly. Mirelo also reported 92 notes for instruments that are not in the song: organ, synth pad, violin and cello. Drum strokes are scored on timing alone, whichever drum. This is one song, rendered rather than recorded, so read it as a direction rather than a ranking.</p>

<h2 id="which-notes-are-wrong">Which notes are most likely to be wrong?</h2>
<p>With Basic Pitch, the ones it wrote with a low velocity. Basic Pitch sets each note’s MIDI velocity to 127 times the model’s mean activation over that note, so velocity doubles as a confidence score. Across the 2,715 notes it wrote for the 12 guitar recordings, matched with the same 50 ms window, low-velocity notes were mostly wrong:</p>
<figure class="bars-figure">
<div class="bars bars-wide" aria-hidden="true">
<p class="bars-title">Notes Basic Pitch got right, by velocity <span>2,715 notes from 12 guitar recordings</span></p>
<div class="bar-row"><span>Below 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>The quieter Basic Pitch wrote a note, the more likely it was wrong. Counts are in the table below.</figcaption>
</figure>
<div class="table-scroll" role="region" aria-label="Basic Pitch note velocity against correctness" tabindex="0">
<table>
<caption>Basic Pitch velocity vs correctness, 2,715 notes</caption>
<thead>
<tr><th scope="col">Velocity</th><th scope="col" class="num">Notes</th><th scope="col" class="num">Share</th><th scope="col" class="num">Correct</th></tr>
</thead>
<tbody>
<tr><th scope="row">Below 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>Velocity separates right from wrong notes with an AUC of 0.77, where 0.5 is a coin toss and 1.0 is perfect; note length manages 0.53, so short notes are not a useful warning sign. Deleting every note below velocity 50 would raise the overall onset F1 from 0.744 to 0.765, but one of those notes in five is correct. ScoreStarling’s score review therefore lists them as doubtful rather than deleting them, and leaves the decision to your ears. If you run Basic Pitch yourself, coloring or sorting notes by velocity in a piano roll gives you the same shortlist.</p>

<h2 id="accuracy-claims-disagree">Why don’t published accuracy figures agree?</h2>
<p>They measure different things on different recordings, and some don’t say what they measured.</p>
<div class="table-scroll" role="region" aria-label="Published accuracy figures and what they measured" tabindex="0">
<table>
<caption>Published figures, checked October 3, 2026</caption>
<thead>
<tr><th scope="col">Source</th><th scope="col">Figure</th><th scope="col">Measured on</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://arxiv.org/abs/2203.09893">Basic Pitch paper</a> (Bittner et al., 2022)</th><td>0.79 onset F1; 0.56 with note ends</td><td>GuitarSet test split, 72 recordings</td></tr>
<tr><th scope="row"><a href="https://arxiv.org/abs/2607.08168">MuScriptor paper</a> (Rouard et al., 2026)</th><td>60.4 onset F1 on a 0–100 scale, against 32.52 for YourMT3+</td><td>372 carefully annotated real recordings from the authors’ own data</td></tr>
<tr><th scope="row"><a href="https://scorecloud.com/learn/how-accurate-is-automatic-music-transcription/">ScoreCloud guide</a></th><td>“pitch accuracy is typically 85-95%”</td><td>Solo instruments and voices; no method given</td></tr>
<tr><th scope="row">This test</th><td>0.744 Basic Pitch, 0.471 Mirelo onset F1</td><td>12 GuitarSet recordings</td></tr>
</tbody>
</table>
</div>
<p>Figures can be compared only when the metric, the tolerance and the recordings match. Our Basic Pitch result can sit beside its paper’s, since both use onset F1 on GuitarSet, though on different subsets; it can’t sit beside MuScriptor’s. The ScoreCloud page doesn’t say how pitch accuracy was counted, on how many recordings, or whether timing mattered. MuScriptor is the open research model that Mirelo’s Audio-to-MIDI Pro grew out of, and Mirelo describes its production model as more accurate. Mirelo’s post of September 17, 2026 says an updated model improved note and instrument accuracy “in our evaluations”, without publishing figures.</p>

<h2 id="accurate-notes-readable-score">Do accurate notes make a readable score?</h2>
<p>No. Onset F1 compares notes in seconds; it says nothing about whether the written score reads well. A score also needs a key signature, sensible sharps and flats, written rests, voices and a meter, and plain MIDI from a model like Basic Pitch carries none of them. On the same Basic Pitch notes, our first notation writer produced scores with no key signature and no visible rests in all 12 cases, while MuseScore Studio’s MIDI importer wrote the annotated key in 10 of 12 and showed every rest. Mirelo’s own MusicXML had the annotated key in 5 of its 9 completed cases, always on a single treble staff.</p>
<p>A transcription can score well and still be hard to read, and a tidy page can still hold wrong notes. The full before-and-after is in <a href="https://scorestarling.com/blog/why-midi-imports-look-messy">Why MIDI imports look messy in MuseScore</a>.</p>

<h2 id="which-engine-to-use">Which transcription engine should you use?</h2>
<p>Choose by the recording. Our reading of these results:</p>
<ul>
<li>One instrument or one melody line: Basic Pitch. It was far ahead on solo guitar, it is free and open source, and <a href="https://github.com/spotify/basic-pitch">its README</a> says it works best on one instrument at a time.</li>
<li>A band, or a mix where you need separate parts or drums: a multi-instrument model such as Mirelo’s, since Basic Pitch writes a single combined part.</li>
<li>Guitar accompaniment: either. They were close on average and each did better on different recordings, so try both on a short excerpt if the choice matters.</li>
<li>Voice: we haven’t measured it yet. Piano: we have measured only our own solo-piano option, on synthesized audio, so we can’t rank engines for it.</li>
</ul>
<p>Whichever engine you use, check rhythm and note lengths by ear. The measurements above, from October 2 and the October 4 re-run, leave out our solo-piano option. ScoreStarling uses Basic Pitch for one instrument or voice and a piano model for solo piano, both free, and Mirelo for a band, which uses credits (checked October 7, 2026). Each covers the first five minutes of an upload of up to 100 MiB. A voice or one instrument starts in 4/4; solo piano works out its meter, and a band score’s meter is found from the playing or chosen before it starts. Guitar, bass and ukulele parts can also be read as TAB, without bends, slides or hammer-ons; see the <a href="https://scorestarling.com/how-it-works#specs">specifications</a>.</p>


<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://zenodo.org/records/3371780">GuitarSet 1.1.0</a> — Xi, Bittner, Pauwels, Ye and Bello, Zenodo (CC BY 4.0)</li>
<li><a href="https://guitarset.weebly.com/">GuitarSet project page</a> — the GuitarSet authors</li>
<li><a href="https://github.com/marl/GuitarSet/issues">GuitarSet issues</a> — marl/GuitarSet, GitHub</li>
<li><a href="https://zenodo.org/records/4599666">Slakh2100</a> — Manilow, Wichern, Seetharaman and 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 et al., 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 et al., Spotify, 2022</li>
<li><a href="https://github.com/spotify/basic-pitch">Basic Pitch</a> and its <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 documentation</li>
<li><a href="https://arxiv.org/abs/2607.08168">MuScriptor: An Open Model for Multi-Instrument Music Transcription</a> — Rouard et al., 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, September 17, 2026</li>
<li><a href="https://mirelo.ai/models/audio-to-midi">Audio to MIDI converter</a> and <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>Questions and answers</h2><h3>Can AI transcribe music from an MP3?</h3><p>Yes, into MIDI note by note, with errors to fix. Models such as Spotify’s Basic Pitch work best on one instrument; full mixes need a multi-instrument model and come out less accurate. In our October 2, 2026 test the best group result was an onset F1 of 0.818, Basic Pitch on solo guitar (0.846 in an October 4 re-run, after two changes to how ScoreStarling uses it), so plan to correct some notes by ear.</p><h3>Is Basic Pitch accurate enough to make sheet music?</h3><p>For a first draft from one instrument, yes. On 12 guitar recordings it scored 0.744 onset F1 for note starts but 0.508 once note ends counted, and its MIDI has no key signature, rests or voices, so a notation step has to add them. <a href="https://scorestarling.com/blog/why-midi-imports-look-messy">Why MIDI imports look messy in MuseScore</a> shows what that step changes.</p><h3>How can I measure transcription accuracy myself?</h3><p>You need a reference transcription you trust, with note times in seconds. With one, the open-source mir_eval library scores note matches with the tolerances we used: 50 ms for the start, 50 cents for pitch and, for note ends, 20% of the note’s length or 50 ms. Without one, loop short passages and compare the score with the recording by ear.</p><h3>Why did Mirelo return no notes for some guitar solos?</h3><p>We don’t know. Three of the six solo recordings came back empty on October 2, 2026. We ran them without Mirelo’s optional instrument list and haven’t retried them. On accompaniment it came close to Basic Pitch, and it led on the multi-instrument excerpt.</p><h3>How accurate is AI transcription for singing or piano?</h3><p>For singing, we can’t give you our own numbers yet. For piano, only on synthesized audio: on the opening bars of 25 pieces, each played twice with rubato (50 performances, measured October 6, 2026), our solo-piano option, built on Kong and colleagues’ piano model, heard about 92% of the written notes but got the beat right in only 35 of the 50, and the beat, meter and pickup in 26. Basic Pitch’s authors built it to work across instruments, voice included. A clear, dry recording of one voice or instrument gives any model its best chance.</p>]]></content:encoded>
    </item>
    <item>
      <title>Why MIDI imports look messy in MuseScore, and how to fix them</title>
      <link>https://scorestarling.com/blog/why-midi-imports-look-messy</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/why-midi-imports-look-messy</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Research</category>
      <description>MIDI stores a performance, not notation. What that did to 12 guitar transcriptions, why MuseScore re-guesses key and tempo, and the prep that fixes most of it.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>A MIDI file records a performance: which key went down, when, how hard and for how long. Its notes have no sharps or flats, voices, written rests or beams, and the file often has no key signature, so MuseScore has to guess them, and it guesses worst when the notes sit off the beat grid. On 12 guitar transcriptions, our first converter wrote no key signature and hid every rest; MuseScore Studio 4.7.5’s importer, given the same notes on a 16th-note grid at a constant tempo with the key written into every track, got the key right in 10 of 12 and showed every rest. MuseScore Studio 4 has no MIDI import panel, so those fixes go into the MIDI file or into an options file on the command line.</p>
</section>

<h2 id="what-midi-does-not-store">What doesn’t a MIDI file store?</h2>
<p>Most of what a reader needs. A MIDI note is a key number from 0 to 127 plus a velocity, sent once when the note starts and again when it stops (<a href="https://midi.org/summary-of-midi-1-0-messages">MIDI Association</a>). Note 63 is D♯ and E♭ at once; which one to write depends on the key, and the note doesn’t say. A Standard MIDI File can add tempo, time signature and key signature as <a href="https://mido.readthedocs.io/en/latest/meta_message_types.html">meta events</a>, but it has no standard place for voices, rests, beams or tuplets. A rest is just a gap, and a length is a count of ticks, not a written value.</p>
<p>Audio-to-MIDI tools add a second problem: their timing comes from a recording, so notes rarely land exactly on a beat. Spotify’s Basic Pitch, which ScoreStarling uses, writes every note into one Electric Piano 1 part at the tempo you give it, with no key signature (<a href="https://github.com/spotify/basic-pitch/blob/main/basic_pitch/note_creation.py">note_creation.py</a>). Turning that into notation means choosing, for every note, a spelling, a written length, a voice and a staff. Every converter makes those choices; they differ in how well.</p>

<h2 id="simple-converter-results">What a simple converter did to 12 guitar transcriptions</h2>
<p>Our first notation step was a converter we wrote on the music21 library. We fed it Basic Pitch MIDI from 12 recordings in the public GuitarSet dataset, with each recording’s tempo supplied, and Verovio engraved exactly what it wrote. An audit of those 12 scores found:</p>
<ul>
<li>No visible rests in any score: by default, music21’s MusicXML exporter fills gaps with hidden rests (<a href="https://github.com/cuthbertLab/music21/blob/master/music21/musicxml/m21ToXml.py">m21ToXml.py</a>).</li>
<li>No key signature in any score. Pieces in E major showed no sharps, and one accompaniment in C major carried redundant naturals on 153 of its 431 written notes (421 MIDI notes, some split by ties).</li>
<li>Spelling by note number alone. MIDI note 63 always came out as E♭, even where E major needs D♯; in a piece in G♭ major, 30 of the 49 notes that belong to the key were misspelled.</li>
<li>Up to five voices on one staff. Note starts and ends were each snapped to the nearest 16th, so a string still ringing under the next note forced another voice, with dots and ties to match.</li>
<li>Guitar written as piano: 10 of the 12 scores on a grand staff and the other 2 in bass clef, none in guitar clef, because Basic Pitch labels every note Electric Piano.</li>
</ul>
<p class="note">We re-ran that converter on the same saved MIDI files on October 7, 2026 and got the same counts.</p>
<p>The music font was not the problem. Verovio and its Leipzig font drew what the MusicXML said; the MusicXML was wrong.</p>

<h2 id="before-and-after">Before and after: MuseScore Studio’s importer</h2>
<p>We looked at open-source alternatives first: partitura handles key, spelling and voices but has no quantizer and writes no rests, and the other research tools we found were piano-only, unlicensed, non-commercial or unreleased. So we handed the job to MuseScore Studio 4.7.5’s MIDI importer, run as a separate program. Same 12 recordings, same Basic Pitch notes, same supplied tempo, prepared as described below, on a Mac on October 2, 2026:</p>
<div class="table-scroll" role="region" aria-label="Engraving 12 GuitarSet transcriptions before and after" tabindex="0">
<table>
<caption>12 GuitarSet transcriptions, before and after</caption>
<thead>
<tr><th scope="col">Measure</th><th scope="col" class="num">music21 converter</th><th scope="col" class="num">MuseScore importer</th></tr>
</thead>
<tbody>
<tr><th scope="row">Key signature matches the annotation</th><td class="num">0/12</td><td class="num">10/12</td></tr>
<tr><th scope="row">Notes in the key’s scale spelled against it</th><td class="num">322 of 2,703</td><td class="num">0 of 3,213</td></tr>
<tr><th scope="row">Accidental signs per written note</th><td class="num">0.53</td><td class="num">0.06</td></tr>
<tr><th scope="row">Visible rests</th><td class="num">0 of 1,626</td><td class="num">988 of 988</td></tr>
<tr><th scope="row">Most voices on one staff</th><td class="num">5</td><td class="num">2</td></tr>
<tr><th scope="row">Every MIDI note written</th><td class="num">12/12</td><td class="num">12/12</td></tr>
<tr><th scope="row">4/4 at the supplied tempo</th><td class="num">12/12</td><td class="num">12/12</td></tr>
</tbody>
</table>
</div>
<p>The written note counts differ because MuseScore ties notes across beats and barlines differently. The two wrong keys were both a fifth from the annotation: D♭ major for a piece in G♭, B major for one in E. Without our estimate in the file, MuseScore’s own key guess matched only 5 of the 12 (re-run October 7, 2026); writing the estimate in is what raised it to 10. The importer took 1.2–4.3 seconds per recording, 2.3 seconds on average.</p>

<h2 id="why-musescore-reguesses">Why does MuseScore re-guess the tempo, meter and key?</h2>
<p>Because unquantized MIDI looks like a live performance to it. The importer checks the notes that fall near a beat, and if fewer than 60% of them start right on it, it treats the file as a human performance (<a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_quant.cpp">importmidi_quant.cpp</a>). It then runs its own beat tracker, picks a meter and stretches note times to fit the beats it found. MuseScore’s older handbook describes that mode as trading accuracy for readability and says it is on by default for unaligned files.</p>
<p>The key has a rule of its own. If any non-drum track lacks a key signature, or the file counts as a performance, the importer estimates the key from the notes (<a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_key.cpp">importmidi_key.cpp</a>). So a key signature in the tempo track alone is not enough: every track with notes needs one. With raw Basic Pitch MIDI we saw meter, tempo and key all re-guessed, even with the human-performance option set to false.</p>

<h2 id="prepare-midi">How should you prepare MIDI before importing it?</h2>
<p>Make the notation decisions you can inside the MIDI file, before MuseScore sees it. This is what we do to every transcription:</p>
<ol>
<li>Use one constant tempo. ScoreStarling uses the BPM you set, or one it detects from the recording; in a DAW, record to a click or match the project tempo to the take.</li>
<li>Move every note’s start and end onto a grid of the shortest value you want to read. The October 2 test here used 16th notes in 4/4. With every note on the grid, MuseScore reads the file as written music rather than as a performance.</li>
<li>Put the tempo and time signature in the first track, and a key signature in every track that has notes. We estimate the key from the notes with music21’s Krumhansl–Schmuckler key finder; if you know the real key, use it.</li>
<li>Give each track its real instrument. MuseScore picks staves and clefs from the General MIDI program: a guitar left as Electric Piano lands on a grand staff, while a nylon-string guitar program gets treble clef with an 8 below.</li>
<li>Watch repeated notes. If one pitch is struck twice within a single grid step, snapping merges the two attacks; we place the second one a 32nd later.</li>
</ol>

<h2 id="musescore-import-settings">Which MuseScore import settings should you use?</h2>
<p>MuseScore Studio 4 has no import panel to set them in. Its handbook calls the MIDI import panel “not currently implemented”, and the GitHub issue to restore it (<a href="https://github.com/musescore/MuseScore/issues/11918">#11918</a>, opened in June 2022) was still open when we checked on October 3, 2026. Inside the app, the only quantization control is the shortest note value for imported MIDI files, under Preferences → Import.</p>
<p>The importer still reads its main options from a file passed on the command line with <code>-M</code>; the <a href="https://handbook.musescore.org/appendix/command-line-usage">handbook’s command-line page</a> links a sample. This is the file the October 2 test used; ScoreStarling now writes a melody line in one voice (<code>VoiceCount</code> 0) and turns <code>RecognizePickupBar</code> on when the first bar is a pickup:</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>Then convert, naming the output as MusicXML:</p>
<pre><code>mscore -M midi-import.xml -o score.musicxml performance.mid</code></pre>
<p>The program’s name and location depend on your system; on Windows the handbook uses MuseScore4.exe. What the main options do:</p>
<div class="table-scroll" role="region" aria-label="MuseScore MIDI import options and what they do" tabindex="0">
<table>
<caption>Our MIDI import options</caption>
<thead>
<tr><th scope="col">Option</th><th scope="col">Value</th><th scope="col">Effect</th></tr>
</thead>
<tbody>
<tr><th scope="row">QuantValue</th><td>2</td><td>Shortest written value: 16th notes (1 is 8ths, 3 is 32nds)</td></tr>
<tr><th scope="row">VoiceCount</th><td>1</td><td>At most two voices per staff; the count is the value plus one</td></tr>
<tr><th scope="row">Triplets and other tuplets</th><td>false</td><td>No tuplet search, because our grid has no tuplets</td></tr>
<tr><th scope="row">HumanPerformance</th><td>false</td><td>Don’t treat the file as a live performance; this holds only when the notes are on the grid</td></tr>
<tr><th scope="row">SplitStaff</th><td>false, true for piano</td><td>Splits a piano part between the hands; for other instruments MuseScore makes separate parts instead</td></tr>
<tr><th scope="row">SimplifyDurations</th><td>true</td><td>Fewer rests and simpler note values</td></tr>
<tr><th scope="row">ClefChanges</th><td>false</td><td>No small clef changes inside a staff</td></tr>
<tr><th scope="row">RecognizePickupBar</th><td>false</td><td>The first bar is a full bar</td></tr>
<tr><th scope="row">Swing</th><td>0</td><td>No swing detection</td></tr>
</tbody>
</table>
</div>
<p>If your music has triplets, don’t snap it to a straight grid, and leave Triplets on; we haven’t measured how well MuseScore finds them. In MuseScore 3 the same choices sat in an import panel, with columns such as Max. voices, Search tuplets and Is human performance.</p>

<h2 id="trade-offs">What does the tidier score cost?</h2>
<p>Some accuracy, mostly in note lengths. These are the trade-offs we measured or know of:</p>
<ul>
<li>MuseScore wrote 26% of the notes longer than the quantized performance, by up to three 16ths and never shorter, and notes sounding together with different lengths can share one value. In ScoreStarling, playback and MIDI exports keep the performed timing, though a melody line is cleaned first: of notes struck together the strongest stays, faint echoes go, and a held note ends where the next begins.</li>
<li>The key is estimated from the notes and was wrong in 2 of 12 cases, each time a fifth away. Keys with seven sharps or flats are written as their five-accidental equivalents.</li>
<li>The straight 16th-note grid in this October 2 guitar test did not represent triplets or swing. That is still true when ScoreStarling transcribes one instrument or voice, which starts in 4/4; for solo piano it follows the playing’s bars and can choose 4/4, 3/4, 6/8 or 12/8. If you prepare a file yourself, write its real time signature in.</li>
<li>MuseScore splits piano chords between the hands by its own rule, and even close two-note chords can land on different staves.</li>
</ul>
<p>Some of this can be cleaned up after import. <a href="https://handbook.musescore.org/notation/rhythm-meter-and-measures/regroup-rhythms">Tools → Regroup rhythms</a> rewrites a selected passage so that notes group by beat, without changing the rhythm itself. <a href="https://handbook.musescore.org/notation/pitch/respell-pitches">Tools → Respell pitches with sharps or with flats</a> changes spelling, though the handbook says it is meant for notes of a single pitch. A wrong key is quicker to fix in the MIDI file before import than note by note afterwards.</p>

<h2 id="mp3-to-musescore">Can MuseScore turn an MP3 into sheet music?</h2>
<p>MuseScore Studio can’t: it opens score formats such as MusicXML and MIDI, not audio. Muse Group announced an <a href="https://www.mu.se/posts/musescore-audio-score-features">Audio-to-Score beta</a> on MuseScore’s web platform on August 17, 2026: solo piano and acoustic guitar, MP3 files up to 30 MB (about three minutes), free, returned as an MSCZ file for MuseScore Studio. We haven’t tested it.</p>
<p>The other route is the one in this article: an audio-to-MIDI model, preparation of its timing, then MuseScore’s importer. ScoreStarling works this way for one instrument or voice, with Basic Pitch, and for solo piano, with a piano model whose notes also give the pulse and bar lines; Verovio draws the page. A single-note line is now written on one staff in one voice, a part playing chords keeps them on one staff, and Score settings → Layout can change how a single-part recording is written. The measurements above are from October 2. An October 4 re-run, after melodies began to be written one note at a time on one staff, again matched the key in 10 of 12, spelled 0 of 3,158 in-key notes against it and showed all 357 rests.</p>
<p>You can also choose or drop a MIDI file in ScoreStarling and get an editable score; it leaves out drum tracks and keeps one time signature for the whole score. A recording is transcribed for its first five minutes, from an upload of up to 100 MiB. PDFs are free and MusicXML needs the score unlocked (15 credits, once); the <a href="https://scorestarling.com/how-it-works#specs">specifications</a> have the rest (checked October 7, 2026). The <a href="https://scorestarling.com/app">workspace</a> is free to start; the <a href="https://scorestarling.com/blog/ai-music-transcription-accuracy">accuracy article</a> keeps the dated guitar and band measurements.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">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 documentation</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 et al., Zenodo (CC BY 4.0)</li>
<li><a href="https://musescore.org/en/handbook/4/midi-import">MIDI import</a> — MuseScore 4 handbook</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> and <a href="https://github.com/musescore/MuseScore/blob/master/src/importexport/midi/internal/midiimport/importmidi_key.cpp">importmidi_key.cpp</a> — MuseScore source, GitHub</li>
<li><a href="https://musescore.org/en/print/book/export/html/22918">MIDI import</a> — earlier MuseScore handbook (import panel)</li>
<li><a href="https://handbook.musescore.org/appendix/command-line-usage">Command line usage</a> and its <a href="https://musescore.org/sites/musescore.org/files/midi_import_options_0.xml">sample MIDI import options file</a> — MuseScore Studio Handbook</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> and <a href="https://handbook.musescore.org/notation/pitch/respell-pitches">Respell pitches</a> — MuseScore Studio Handbook</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, August 17, 2026</li>
</ol>
</section><h2>Questions and answers</h2><h3>How do I convert MIDI to MusicXML?</h3><p>Open the MIDI file in MuseScore Studio, choose File → Export and pick MusicXML; compressed .mxl is the default. From a terminal, mscore -o score.musicxml file.mid does the same, and -M adds an options file for quantization and voices. Prepare the MIDI first, or the MusicXML will carry the same mess.</p><h3>Why are there so many ties and dotted notes after a MIDI import?</h3><p>Because performed notes rarely end on a beat. Snapped to a fine grid, they cross beats and barlines and need ties, and notes that overlap spill into extra voices. Quantize to the shortest value you want to read, cap the importer at two voices and let it simplify durations; MuseScore will then write some notes slightly longer to tidy the page.</p><h3>Why does MuseScore write D♯ where I expected E♭?</h3><p>MIDI stores note 63 without a spelling, so MuseScore spells from the key signature, and if a track has none it estimates the key itself. Write the right key signature into every track before importing. After import, Tools → Respell pitches with sharps or with flats changes selected notes of the same pitch.</p><h3>Can I get the MuseScore 3 MIDI import panel in MuseScore Studio 4?</h3><p>Not as of October 3, 2026: the panel hasn’t been ported, and <a href="https://github.com/musescore/MuseScore/issues/11918">GitHub issue #11918</a> to restore it is still open. MuseScore Studio 4 keeps a shortest-note preference for imported MIDI, and its command line accepts an options file with -M that covers the panel’s main choices.</p><h3>Can I open a ScoreStarling transcription in MuseScore?</h3><p>Yes. Export MusicXML (.musicxml or .mxl), which keeps the key signature, spelling, rests and voices, or MIDI if you only need the notes. Both need the score unlocked, which costs 15 credits once. ScoreStarling is free to start, and every transcription is a first draft to check by ear.</p>]]></content:encoded>
    </item>
    <item>
      <title>How to turn a voice memo into sheet music</title>
      <link>https://scorestarling.com/blog/voice-memo-to-sheet-music</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/voice-memo-to-sheet-music</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Guide</category>
      <description>Record one voice at a steady tempo, export the .m4a, supply the BPM or let it be detected, then check the draft by ear. Tips, steps and current limits.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>Record the melody on its own: one voice or instrument, phone close, quiet room, steady tempo, ideally with a click in headphones so you know the BPM. Export the memo (an iPhone exports .m4a by default), trim it so it starts on the first note, and give it to a transcription tool along with that tempo. Then treat the score as a first draft: loop two bars at a time against the recording, fix pitches and lengths, and only then transpose and export PDF, MusicXML or MIDI.</p>
</section>

<h2 id="can-an-app-turn-humming-into-sheet-music">Can an app turn humming or singing into sheet music?</h2>
<p>Yes, when the recording holds one clear melody. The software does two separate jobs. First it listens for notes: each pitch, when it starts and when it stops. Then it writes those notes down, which means choosing a key, a meter, note values and where the bar lines go. If the tune is still only in your head, sing it into your phone first; everything below applies to that recording.</p>
<p>In ScoreStarling, for one instrument or voice, the first job is done by Spotify’s open-source Basic Pitch model, which turns audio into MIDI on our server. Spotify says Basic Pitch works on “almost any instrument, including voice.” That is their claim. Our own measurements so far cover guitar recordings, one band excerpt and synthesized piano, not singing. The second job goes to MuseScore Studio 4.7.5’s MIDI importer, and Verovio engraves the page.</p>
<p>Timing is often the hardest part of a voice memo. ScoreStarling detects the tempo or uses the BPM you supply, but for one instrument or voice it still writes to a straight grid starting in 4/4, so drift, silence before the tune and a wrong tempo can produce misplaced ties and rests. Solo piano follows the playing’s own pulse and meter, but it is meant for piano, not sung memos. The advice here helps give a melody a steady pulse.</p>

<h2 id="how-to-record-a-voice-memo-for-transcription">How to record a voice memo for transcription</h2>
<p>Give the software one line of music at one steady tempo, close to the microphone, with nothing else playing.</p>
<ul>
<li>Record one voice or one instrument: no backing track, no second singer, no guitar under the vocal. Busy mixes, heavy reverb and several instruments at once make the first draft harder to read.</li>
<li>Keep the phone close and the room quiet. Apple’s guide sets the recording level by distance: move the phone closer to or farther from the sound. Close, but not so close that your loudest notes distort. A room with soft furnishings echoes less than a bathroom or a stairwell.</li>
<li>Use a click that you can hear and the phone can’t: a metronome in headphones, played from a second device. Apple’s guide says Voice Memos stops recording if another app on the iPhone starts playing audio, so the click shouldn’t come from the recording phone.</li>
<li>Write down the BPM. If you recorded without a metronome, tap along afterward with a tap-tempo metronome, or count beats for 15 seconds and multiply by four.</li>
<li>Count in silently. A spoken “one, two, three, four” is sound like any other and can end up written as notes, so count in your head or in the headphones, or trim the count-in off afterward.</li>
<li>Sing on a syllable such as “da” or “doo” rather than a closed hum, so repeated notes don’t blur into one. That’s our suggestion, not a measured result.</li>
<li>Keep each take under five minutes: only the first five minutes of a recording are transcribed, from a file of up to 100 MiB. Record long songs in sections beginning at a bar line.</li>
<li>Leave the extras off. On Pixel 9 and later, Google’s Recorder can add AI background music to a recording, and added music is more sound to transcribe. If you sang over an instrumental with Voice Memos’ layered recording, separate the layers and upload only the vocal.</li>
</ul>
<p>Then trim the start. Bar 1 begins at 0:00 of the file, so cut any silence or count-in until the file starts right on the first note. If the tune begins with a pickup, you can mark the first bar as a pickup later. In Voice Memos, the editor’s yellow trim handles do this, and you can pinch to zoom for precision. Google’s Recorder has Crop &amp; Remove, which saves a trimmed copy.</p>

<h2 id="what-format-is-a-voice-memo">What format do iPhone and Android voice memos use?</h2>
<p>An iPhone exports a Voice Memos recording as an .m4a file by default; Android recorders differ by maker, so check the file extension after you share one.</p>
<p>Apple’s iPhone User Guide (iOS 27, checked October 3, 2026) says default exports are .m4a, which flattens layered recordings and turns Spatial Audio into stereo. To get the file off the phone, use Save to Files from the recording’s share options. Skip the Editable option: it exports QuickTime Audio, a format for adjusting effects and layers later. For a layered recording, use Separate Layers first; each layer then becomes its own .m4a.</p>
<p>On Pixel phones, Google’s Recorder shares a recording as an audio file: touch and hold it, then choose Share, File and Audio. Google’s help pages don’t name the format (checked October 3, 2026), and other Android brands ship their own recorder apps. Whatever arrives, ScoreStarling accepts WAV, MP3, FLAC, M4A, OGG and AIFF. You can also skip the file and record in the browser; that recorder stops at 4:59, just inside the five-minute limit.</p>

<h2 id="voice-memo-to-sheet-music-steps">Step by step: voice memo to sheet music in ScoreStarling</h2>
<p>Upload the memo, give its tempo, transcribe, then correct the score by ear before you export it. Transcribing one instrument or voice, editing and PDF downloads are free on ScoreStarling; unlocking the score’s MusicXML and MIDI uses credits (checked October 7, 2026). Open the <a href="https://scorestarling.com/app">workspace</a> to start.</p>
<ol>
<li>Choose New score. Drop in the file, or choose Record to sing straight into the browser. Nothing is kept until you choose Create score.</li>
<li>Under What’s playing?, keep One instrument or voice, which runs Basic Pitch on our server for free. Solo piano is free too. Band or several instruments uses credits, 40 a minute with the price shown before it starts, and sends the audio to our band partner Mirelo; it runs only if you pick it.</li>
<li>Open More options and enter Beats per minute, in quarter notes from 30 to 240, or leave it on Auto to have the tempo detected. It sets how the score is written, not the recording. If you can’t decide between 70 and 140, either will do: 140 writes every note at twice the value.</li>
<li>Choose Create score. The score appears with playback in sync with your recording.</li>
<li>Check it by ear, two bars at a time, as the next section describes.</li>
<li>Fix what you hear. Select a note to raise or lower it a semitone (the up and down arrow keys work too), change its length, or delete it; deleting leaves a rest, so the beat stays put. Undo, redo and saved versions are there if a fix goes wrong.</li>
<li>Finish in Score settings: key and clef, time signature, a pickup bar if the tune starts before the downbeat, and transposition by interval. Transpose last. It moves every note and the key, but your recording keeps its original key, so comparing by ear gets harder afterward.</li>
<li>Download what you need: PDF to print, MusicXML for a notation app, MIDI for a DAW, page images, separate parts, or an MP3 or WAV preview. MusicXML and MIDI need the score unlocked (15 credits, once); until then the PDF, page images and parts carry a small footer line. The audio is a General MIDI rendering of the score, not your voice. Our guide to <a href="https://scorestarling.com/blog/musicxml-vs-midi-vs-pdf">MusicXML, MIDI and PDF</a> explains which file to pick.</li>
</ol>

<h2 id="check-transcription-by-ear">How to check a transcription against the recording</h2>
<p>Work two bars at a time: select them, turn on looping, and listen three ways before you change anything. The player switches between Score, Both and Recording while it plays. Start with the recording alone to fix the melody in your ear, then the score alone, then both together. A wrong pitch clashes with your voice; a note in the wrong place sounds doubled, like an echo.</p>
<p>Fix things in order. Check the bar lines first, because if the first beat is wrong, every bar is wrong: mark a pickup bar, or re-trim the file and transcribe again. Then fix pitches, then lengths, and leave expression and layout for last.</p>
<div class="table-scroll" role="region" aria-label="Voice memo checklist" tabindex="0">
<table>
<caption>Voice memo checklist</caption>
<thead><tr><th scope="col">Stage</th><th scope="col">Check</th><th scope="col">Why</th></tr></thead>
<tbody>
<tr><th scope="row">Before you record</th><td>One voice or instrument, nothing else playing</td><td>The One instrument or voice choice is built for a single part</td></tr>
<tr><th scope="row">Before you record</th><td>A click in headphones from a second device; note the BPM</td><td>A supplied BPM is more reliable than the detected estimate</td></tr>
<tr><th scope="row">Before you record</th><td>Phone close, quiet room, little echo</td><td>Noise and reverb make the draft harder to read</td></tr>
<tr><th scope="row">Before you upload</th><td>Trimmed to the first note, within your account’s duration limit</td><td>Start at a clear beat; five minutes a piece</td></tr>
<tr><th scope="row">When you check</th><td>Bar lines and pickup</td><td>A wrong first beat shifts every bar</td></tr>
<tr><th scope="row">When you check</th><td>Repeated and held notes</td><td>Same-pitch notes can merge; lengths are simplified for reading</td></tr>
<tr><th scope="row">When you check</th><td>Key signature</td><td>It’s estimated from the notes and can be a fifth off</td></tr>
<tr><th scope="row">Before you export</th><td>Transpose last</td><td>The recording stays in its original key</td></tr>
</tbody>
</table>
</div>

<h2 id="what-to-expect-first-draft">What to expect from the first draft</h2>
<p>Expect a draft to correct, not a finished part. We haven’t measured singing yet, so this list comes from how the One instrument or voice choice works and from our measurements on 12 solo and accompaniment guitar recordings from the public GuitarSet dataset, run on October 2, 2026 and again on October 4.</p>
<ul>
<li>One instrument or voice starts in 4/4 on a straight grid at the detected or supplied tempo; triplets and swing are not inferred. Check the rhythm and change the time signature in Score settings where needed. Solo piano can follow 3/4, 4/4, 6/8 and 12/8, but it is for piano.</li>
<li>Note ends are less reliable than note starts. On those guitar recordings on October 2, Basic Pitch scored a macro onset F1 of 0.744, but 0.508 when the end of each note had to match too. An October 4 re-run, after we began retuning off-pitch takes and writing a melody one note at a time, scored 0.763 and 0.522. Onset F1 counts a note as found when its pitch matches and its start lands within 50 milliseconds, then balances missed notes against invented ones; 1.0 is perfect. Our <a href="https://scorestarling.com/blog/ai-music-transcription-accuracy">accuracy measurements</a> explain the method.</li>
<li>An out-of-tune take is corrected first. A recording that is steadily 10 cents or more sharp or flat of A440 is retuned before transcription, so its notes are written at concert pitch; a voice whose pitch drifts is left as it is.</li>
<li>Written lengths are simplified for reading. MuseScore Studio wrote 26% of the guitar notes longer than they were played (after snapping to the grid), by up to three sixteenths, and never shorter. Playback and the MIDI export keep your timing; change a note’s length if the page should show what you sang.</li>
<li>Repeated notes need a look. Two notes on one pitch with no gap between them give the model little to separate, so check for one long note where you sang two. To fix it, shorten the note, then fill the rest that appears with the same pitch.</li>
<li>The key is a guess. It’s estimated from the notes; on the guitar set it was wrong for 2 of the 12 recordings, both times a fifth away. Set the right one under Key &amp; clef.</li>
<li>Check the melody line. A detected single-note line is written as Melody on one staff, keeping the strongest of notes struck together and dropping faint echoes, while a part playing chords keeps them on one staff. For a single-part recording, Score settings → Layout can apply One melody line to an older score too, using the existing notes. It removes extra notes from playback; Undo restores them. Check that it kept the notes you sang.</li>
<li>You get notes only. ScoreStarling doesn’t write lyrics or chord symbols from a recording.</li>
</ul>

<h2 id="write-a-melody-down-by-ear">How to write a melody down by ear instead</h2>
<p>If no tool suits the job, you can write the melody down yourself with a keyboard (a phone app will do) and a player that loops a passage. It’s slower, and it trains your ear.</p>
<ol>
<li>Find home. Hum the note the tune wants to end on and find it on the keyboard. That is usually the key’s home note, which suggests a key signature to try.</li>
<li>Find the beat. Tap your foot to the recording and count. A strong beat every three taps suggests 3/4, every four taps 4/4; if each beat splits into three, think 6/8.</li>
<li>Write the rhythm first, one bar at a time, as marks under the counts.</li>
<li>Add the pitches. Start from a note you know, judge each next note as a step, a skip or a leap, and check it on the keyboard.</li>
<li>Play your version back against the recording and sing along. Anything that rubs is a note to fix.</li>
</ol>
<p>Other apps take singing too. ScoreCloud’s Express app for iPhone and iPad notates singing, whistling or humming, and Klangio’s Sing2Notes runs in a browser and on iOS and Android, with PDF, MIDI and MusicXML downloads (both as described on their own sites, checked October 3, 2026). We haven’t tested either.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">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 Support, iPhone User Guide (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 Support</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 Support</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 Support</li>
<li><a href="https://support.google.com/pixelphone/answer/16267696?hl=en">Save &amp; share recordings &amp; transcripts</a> — Pixel Phone Help, 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 Phone Help, 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>Questions and answers</h2><h3>Can I hum, or do I need to sing the words?</h3><p>Both give the software a pitched melody to follow, and we haven’t measured either yet. We suggest singing on a syllable that starts with a consonant, such as “da”, so every note has a clear beginning. That matters most when the same pitch repeats.</p><h3>Will it work out the tempo for me?</h3><p>Yes, it detects the tempo when you leave Beats per minute on Auto. Supplying the BPM you recorded with is more reliable: in our October 3, 2026 check on 14 test recordings, the detected tempo was within 4% on 6, and most misses were off by 3:2 or 4:3, typical of swing or a triplet feel. One instrument or voice starts in 4/4 on a straight grid; if the tune uses another meter, change it in Score settings after transcription.</p><h3>Can it add lyrics or chord symbols to my melody?</h3><p>Not from the recording. The transcription contains notes, not words or chord names. Unlock the score (15 credits, once), export MusicXML and add lyrics or chord symbols in a notation app such as MuseScore Studio.</p><h3>How long can a voice memo be?</h3><p>Up to five minutes. A longer file, up to 100 MiB, is accepted and its first five minutes are transcribed; the browser recorder stops at 4:59. Split longer music at a bar line and transcribe each section.</p><h3>Where does my recording go when I upload it?</h3><p>With One instrument or voice, the default, the audio is transcribed on ScoreStarling’s own servers and the project stays private to your account. Only if you choose Band or several instruments is the audio sent to our band partner Mirelo. The <a href="https://scorestarling.com/privacy">privacy policy</a> covers storage and deletion.</p>]]></content:encoded>
    </item>
    <item>
      <title>MCP servers for sheet music: what each one does</title>
      <link>https://scorestarling.com/blog/sheet-music-mcp-servers</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/sheet-music-mcp-servers</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Engineering</category>
      <description>We opened 26 sheet music MCP servers on October 3, 2026: recording to score, rendering notation, MuseScore and Dorico control, music21 analysis.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>We opened 26 MCP servers for sheet music on October 3, 2026, and they do five different jobs. Two hosted servers turn a recording into notation, Mirelo and our own ScoreStarling, and audio2score-mcp does it on your computer with open-source parts. MuseScore, Dorico and Finale each have servers that drive the app on your machine; mcp-musescore and mcp-score are the active MuseScore ones. The rest render notation an assistant writes, analyze music with music21 and similar engines, or read and find printed music.</p>
</section>

<h2 id="how-we-checked">How did we find and check these servers?</h2>
<p>We searched the official MCP Registry, GitHub, PulseMCP, Glama and mcp.so for sheet music, notation, MuseScore, music21, MusicXML, ABC, LilyPond, Dorico and audio-to-MIDI servers, then opened the README or product page of every server listed here. The directories turned up little on their own: PulseMCP returned one server for “sheet music” and none for “music21”, so most of the list came from GitHub.</p>
<p>In the tables, last activity is the latest release, shown with its version, or else the latest commit on the main branch. “Local” means the server runs on your computer, usually over stdio, and “ChatGPT” means its developer mode. We sent each hosted server an MCP initialize request, the first message any client sends, to see whether it asks for sign-in. We didn’t install or run the local servers, so their features are as their authors describe them. We left out servers that only generate MIDI or audio, DAW controllers, streaming and metadata servers, practice apps, copies of other projects, and repositories whose README doesn’t describe a working server. ScoreStarling is ours, so it appears with the same columns and its limits.</p>

<h2 id="recording-to-notation">Which MCP servers turn a recording into sheet music?</h2>
<p>Three of the servers we opened take a recording and return notation: Mirelo and ScoreStarling are hosted, and audio2score-mcp runs locally. Three more stop short of a score: audiolla and a MuScriptor server return MIDI, and fingerstyle-tab-mcp writes text tablature.</p>
<div class="table-scroll" role="region" aria-label="MCP servers that turn a recording into notation or MIDI" tabindex="0">
<table>
<caption>Recording to notation or MIDI, checked October 3, 2026</caption>
<thead>
<tr><th scope="col">Server</th><th scope="col">What it does</th><th scope="col">Runs</th><th scope="col">Sign-in</th><th scope="col">Clients named</th><th scope="col">Last activity</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://scorestarling.com/mcp">ScoreStarling</a> (ours)</th><td>Recording, sheet music or notation text to an editable score; edits, previews, staff/TAB/jianpu, notation/audio exports. One instrument or voice, or solo piano, free; a band uses credits; the first five minutes of a recording; 100 MiB; tempo detected unless you give one. <a href="https://scorestarling.com/how-it-works#specs">Current limits</a>; <a href="https://scorestarling.com/mcp#webmcp">browser WebMCP Beta</a> is separate</td><td>Hosted</td><td>OAuth; free account</td><td>Claude, ChatGPT, Claude Code, Codex, Cursor, VS Code, Gemini CLI, Devin Desktop, Hermes</td><td>Updated Oct 8, 2026</td></tr>
<tr><th scope="row"><a href="https://mirelo.ai/mcp">Mirelo MCP</a></th><td>Audio-to-MIDI on a recording, returning MIDI and MusicXML, tempo detected; mostly sound-effect tools</td><td>Hosted</td><td>OAuth</td><td>Claude, ChatGPT, Cursor, Claude Code, Codex</td><td>Launched Jul 30, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/David7ce/audio2score-mcp">audio2score-mcp</a></th><td>Basic Pitch to MIDI, then music21 to MusicXML files</td><td>Local</td><td>None</td><td>Claude Code</td><td>Aug 20, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/psyb0t/docker-audiolla">audiolla</a></th><td>Audio to MIDI with Basic Pitch, among dozens of audio tools; no notation</td><td>Self-hosted, HTTP</td><td>Optional token</td><td>Any</td><td>v1.1.7, Aug 1, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/ALLLLLL703/midi">midi</a> (MuScriptor)</th><td>Audio to multi-instrument MIDI with the MuScriptor model; no notation</td><td>Local, Linux</td><td>None</td><td>Not named</td><td>Aug 4, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/blooper20/fingerstyle-tab-mcp">fingerstyle-tab-mcp</a></th><td>Guitar recording to text tablature, with Demucs and Basic Pitch</td><td>Local</td><td>None</td><td>Claude Desktop</td><td>Jan 15, 2026</td></tr>
</tbody>
</table>
</div>
<p>The two hosted servers are built for different jobs. Mirelo’s is mostly sound-effect tools; its Audio-to-MIDI tool returns files and detects the tempo unless you give one, and its plugin README lists no tools for editing a score. ScoreStarling detects the tempo unless you give one and keeps the score in your account, where you can correct notes, hear a change before applying it, transpose and export; it is waiting for review in the ChatGPT and Claude directories. Mirelo is also our band partner inside ScoreStarling. audio2score-mcp stops at files on purpose and leaves editing to your notation app. The MuScriptor server needs a Hugging Face sign-in to download its model, whose weights are for non-commercial use.</p>

<h2 id="render-or-generate-notation">Which MCP servers render or write notation?</h2>
<p>Five servers turn notation that the assistant writes, as ABC, LilyPond, JSON or MusicXML, into something you can see, hear or print. None of them transcribes a recording.</p>
<div class="table-scroll" role="region" aria-label="MCP servers that render or write notation" tabindex="0">
<table>
<caption>Rendering or writing notation, checked October 3, 2026</caption>
<thead>
<tr><th scope="col">Server</th><th scope="col">What it does</th><th scope="col">Runs</th><th scope="col">Sign-in</th><th scope="col">Clients named</th><th scope="col">Last activity</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 example)</th><td>ABC notation to a playable score with abcjs; one tool</td><td>Local, or a hosted demo</td><td>None</td><td>MCP Apps hosts</td><td>v2.0.3, Sep 25, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/linxule/mcp-music-studio">MCP Music Studio</a></th><td>ABC to sheet music with playback, transposition, MIDI and WAV download; Strudel live coding</td><td>Hosted or local</td><td>None</td><td>Claude, Claude Code, Codex CLI, Gemini CLI, Cursor, VS Code, Windsurf</td><td>v0.9.2, Oct 2, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/delmas41/gradusnotation">Gradus Notation</a></th><td>JSON score to SVG, MusicXML and MIDI; harmonic analysis; engraving checks</td><td>Local, calls Gradus’s API</td><td>None</td><td>Claude Code, Claude Desktop</td><td>v0.8.0, Aug 26, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/wspringer/lilypond-mcp">lilypond-mcp</a></th><td>LilyPond code to cropped PDF, EPS, SVG or PNG</td><td>Local</td><td>None</td><td>Claude Code, Claude Desktop</td><td>v0.2.2, Sep 9, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/raulkivi/sheet-music-mcp">sheet-music-mcp</a></th><td>Six servers for choirs: photo to MusicXML, rendering, part playback, ABC editing, edition diffs, following a singer</td><td>Local</td><td>None</td><td>Claude Desktop, Claude Code, Cursor, Windsurf, Continue, Zed</td><td>v0.2.1, Sep 26, 2026</td></tr>
</tbody>
</table>
</div>
<p>The official example is the smallest: its one tool, <code>play-sheet-music</code>, takes ABC and draws it in hosts that support MCP Apps, and a copy at <code>example-server.modelcontextprotocol.io</code> answered our handshake without sign-in. Music Studio’s hosted address did too. Gradus is a local package that sends scores to its maker’s hosted API without a key and asks agents to credit Gradus when they show its output; it also analyzes harmony and checks engraving against a cited rulebook. lilypond-mcp runs a WebAssembly build of LilyPond, and raulkivi’s rendering server uses Verovio, the engraver we use too.</p>

<h2 id="musescore-dorico-finale">Is there an MCP server for MuseScore, Dorico or Finale?</h2>
<p>Yes: we found five for MuseScore, two for Dorico and one for Finale, all independent projects on GitHub that run on your computer next to the app.</p>
<div class="table-scroll" role="region" aria-label="MCP servers that control MuseScore, Dorico or Finale" tabindex="0">
<table>
<caption>Controlling notation software, checked October 3, 2026</caption>
<thead>
<tr><th scope="col">Server</th><th scope="col">What it does</th><th scope="col">Runs</th><th scope="col">Sign-in</th><th scope="col">Clients named</th><th scope="col">Last activity</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://github.com/ghchen99/mcp-musescore">mcp-musescore</a></th><td>Drives an open MuseScore 3 or 4 score: notes, rests, tuplets, lyrics, measures</td><td>Local, with a MuseScore plugin</td><td>None</td><td>Claude Desktop</td><td>Oct 2, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/tskovlund/mcp-score">mcp-score</a></th><td>Writes MusicXML through music21; edits a live MuseScore Studio 4.4.2+ score; renders files; Dorico experimental</td><td>Local, with a MuseScore plugin</td><td>None</td><td>Claude Code, Claude Desktop, LM Studio</td><td>v0.1.0, Sep 25, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/JordanSucher/musescore-mcp">musescore-mcp</a> (JordanSucher)</th><td>Basic note and rest entry in an open MuseScore score</td><td>Local, with a MuseScore plugin</td><td>None</td><td>Claude Desktop</td><td>Apr 20, 2025</td></tr>
<tr><th scope="row"><a href="https://github.com/strongbeen04/MUSESCORE-MCP">musescore-mcp</a> (strongbeen04)</th><td>Wraps MuseScore’s command line: convert, export parts, transpose, MIDI to a MuseScore file</td><td>Local</td><td>None</td><td>Claude Desktop, Claude Code</td><td>May 10, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/achou666666-code/ScoreBridge">ScoreBridge</a></th><td>Turns printed music the assistant reads in a PDF or image into an editable MuseScore file</td><td>Local</td><td>None</td><td>Not named</td><td>v0.1.0, Oct 1, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/happycastle114/dorico-mcp-server">dorico-mcp-server</a></th><td>Note entry, notation, harmony and orchestration tools in Dorico</td><td>Local</td><td>Dorico setting</td><td>Claude Desktop, ChatGPT Desktop</td><td>v0.3.0, Jan 6, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/romanstark/dorico-maestro">Dorico Maestro</a></th><td>Note entry at the caret, MusicXML scores, voice-leading audits, PDF export in Dorico 4 to 6</td><td>Local</td><td>Dorico prompt</td><td>Claude Desktop, Cursor, Antigravity</td><td>v0.1.6, Sep 10, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/The-INTJ/FinaleMCP">FinaleMCP</a></th><td>Engraving and formatting in an open Finale document, each step undoable</td><td>Local, with a Lua bridge</td><td>None</td><td>Claude Code, Claude Desktop</td><td>Jul 24, 2026</td></tr>
</tbody>
</table>
</div>
<p>They work through each app’s own interfaces: MuseScore’s plugins, extension host or command line; Dorico’s Remote Control API, which you allow in Dorico, through a preference or a prompt on first connection; or an RGP Lua script in Finale. FinaleMCP’s README notes that Finale was discontinued in August 2024 but still runs. None of them imports audio. JordanSucher’s server still appears in directories but hasn’t changed since April 2025, so we would try mcp-musescore and mcp-score first; mcp-score calls itself alpha. The only official MCP Registry entry with “musescore” in its name, <a href="https://www.npmjs.com/package/@chrischall/musescore-mcp">@chrischall/musescore-mcp</a>, is a local package for Claude that searches the musescore.com site through your signed-in browser instead of editing scores; it was last published on September 28, 2026, and its GitHub repository returned 404 when we checked.</p>

<h2 id="music21-analysis">Is there a music21 MCP server for music analysis?</h2>
<p>Several. music21 is, in its own documentation’s words, “a Python-based toolkit for computer-aided musicology,” and these servers use it or similar engines to answer questions about keys, chords, cadences and voice leading in MIDI, MusicXML or MEI files.</p>
<div class="table-scroll" role="region" aria-label="MCP servers for music analysis" tabindex="0">
<table>
<caption>Music analysis, checked October 3, 2026</caption>
<thead>
<tr><th scope="col">Server</th><th scope="col">What it does</th><th scope="col">Runs</th><th scope="col">Sign-in</th><th scope="col">Clients named</th><th scope="col">Last activity</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://github.com/brightlikethelight/music21-mcp-server">music21-mcp-server</a></th><td>13 tools: key, Roman numerals, voice leading, patterns, harmonization, counterpoint</td><td>Local</td><td>None</td><td>Claude Desktop</td><td>v1.0.0, Mar 18, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/SimonsonM/music21-mcp">music21-mcp</a> (SimonsonM)</th><td>Seven tools: key, progressions, cadences, harmonization, counterpoint, MIDI</td><td>Local</td><td>None</td><td>Claude Desktop, Claude Code</td><td>Apr 18, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/cclawton/music21-mcp">music21-mcp</a> (cclawton)</th><td>16 tools to read, edit and analyze MIDI files</td><td>Local</td><td>None</td><td>Not named</td><td>Jul 14, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/unimelbmdap/encoding-music-mcp">encoding-music-mcp</a></th><td>Analyzes MEI files, 46 included, and renders them with Verovio</td><td>Local</td><td>None</td><td>Claude Desktop</td><td>Aug 31, 2026</td></tr>
<tr><th scope="row"><a href="https://github.com/BluesPrince/thiri-mcp">THIRI Chord Intelligence</a></th><td>Chord analysis, voicings and reharmonization; noncommercial license</td><td>Hosted or local</td><td>API key</td><td>Claude, Cursor</td><td>v0.5.3, Aug 9, 2026</td></tr>
</tbody>
</table>
</div>
<p>The most-starred, music21-mcp-server, needs a source install: the pip package its README names wasn’t on PyPI when we checked, and its main branch last changed on March 19, 2026. It also offers a local HTTP API beside the MCP server. encoding-music-mcp comes from the University of Melbourne’s data analytics platform and suits scholarly MEI editions. THIRI answers chord questions rather than reading whole scores.</p>

<h2 id="other-notable">Which other sheet music MCP servers are worth knowing?</h2>
<p>Two jobs sit next to transcription: reading printed music into a digital score, and finding public-domain scores.</p>
<div class="table-scroll" role="region" aria-label="MCP servers for reading and finding printed music" tabindex="0">
<table>
<caption>Reading and finding printed music, checked October 3, 2026</caption>
<thead>
<tr><th scope="col">Server</th><th scope="col">What it does</th><th scope="col">Runs</th><th scope="col">Sign-in</th><th scope="col">Clients named</th><th scope="col">Last activity</th></tr>
</thead>
<tbody>
<tr><th scope="row"><a href="https://melogenai.com/mcp">Melogen</a></th><td>Printed sheet music (PDF, PNG, JPG) to MIDI and MusicXML; song analysis of tempo, key and structure</td><td>Hosted</td><td>OAuth</td><td>ChatGPT</td><td>Not dated</td></tr>
<tr><th scope="row"><a href="https://github.com/smeet666/mcp-imslp">mcp-imslp</a></th><td>Searches IMSLP’s public-domain works, editions and composers, and links to them</td><td>Local</td><td>None</td><td>Claude Code, Claude Desktop, Cursor, VS Code</td><td>v1.0.1, Aug 29, 2026</td></tr>
</tbody>
</table>
</div>
<p>To read printed music on your own computer instead, the omr-mcp server in raulkivi’s set above turns a photo or scan into MusicXML, and ScoreBridge, in the MuseScore table, has the assistant read the page and write it out through MuseScore. All of them work from images of a page, not from audio.</p>

<h2 id="how-to-choose">How do you choose a sheet music MCP server?</h2>
<p>Start from the job, then check where the server runs.</p>
<ul>
<li>For an editable score from a recording inside a chat app, use a hosted recording-to-notation server: ScoreStarling with a free account, or Mirelo with a Mirelo account. Compare how each sets the tempo, how long a take can be and which files come back.</li>
<li>To keep working in MuseScore, Dorico or Finale, use one of the app servers. They need the app installed on the same computer, usually open, and a client that runs local servers.</li>
<li>For analysis or teaching, try a music21 server or Gradus; for MEI editions, encoding-music-mcp.</li>
<li>For the assistant to write and show music, use Music Studio or the Sheet Music Server example for ABC, or lilypond-mcp for LilyPond.</li>
</ul>
<p>Hosted servers are the kind you can add to ChatGPT, or to Claude on the web and on a phone, but your files go to the provider, and most need an account with it. ChatGPT’s developer mode connects with OAuth or no authentication, while Claude’s custom connector form also takes fixed request headers such as an API key (both checked October 3, 2026). Local servers keep files on your machine and need Python or Node and a desktop client such as Claude Desktop, Claude Code, Cursor or VS Code. Either way, read the last-activity column: four of the GitHub projects here had no commit on their main branch in the six months before we checked.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">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 Help Center</li>
<li><a href="https://www.music21.org/music21docs/about/what.html">What is music21?</a> — music21 documentation</li>
<li><a href="https://mirelo.ai/mcp">Mirelo MCP</a>, <a href="https://mirelo.ai/blog/introducing-mirelo-mcp">its launch post</a> and <a href="https://github.com/mirelo-ai/mirelo-plugins">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>READMEs on GitHub: <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>Questions and answers</h2><h3>Which MuseScore MCP server is the most popular?</h3><p>By GitHub stars on October 3, 2026, ghchen99’s <a href="https://github.com/ghchen99/mcp-musescore">mcp-musescore</a> leads with 105, then JordanSucher’s musescore-mcp with 29, which hasn’t changed since April 2025, and tskovlund’s mcp-score with 26. Stars measure attention, not quality, so also check the last commit and which MuseScore versions each one supports.</p><h3>Can I use these MCP servers in ChatGPT?</h3><p>On the web, only the hosted ones. ChatGPT’s developer mode, offered on Plus, Pro, Business, Enterprise and Education accounts, connects to remote servers over SSE or streaming HTTP with OAuth or no authentication (checked October 3, 2026). Local stdio servers need a client that starts them on your computer, such as Claude Desktop, Claude Code, Cursor or VS Code.</p><h3>Is there an MCP server that turns audio into guitar tab?</h3><p>fingerstyle-tab-mcp writes text tablature from a guitar recording, using Demucs and Basic Pitch on your own computer, with setup steps for Claude Desktop. Its last commit was on January 15, 2026. Since October 8, 2026, ScoreStarling also shows a guitar, bass or ukulele part as TAB under the staff or alone and returns the TAB as text.</p><h3>Do MuseScore, Steinberg or MakeMusic publish an official MCP server?</h3><p>Not among the servers we found on October 3, 2026. Every MuseScore, Dorico and Finale server here is an independent project on GitHub, and Dorico Maestro’s README says it isn’t affiliated with Steinberg.</p><h3>Do I need MuseScore installed to use a MuseScore MCP server?</h3><p>Yes. mcp-musescore needs MuseScore 3 or 4 running with its plugin and a score open. mcp-score needs MuseScore Studio 4.4.2 or later running for live editing, and MuseScore installed, though not running, to render files.</p>]]></content:encoded>
    </item>
    <item>
      <title>MusicXML vs MIDI vs PDF: which file do you need?</title>
      <link>https://scorestarling.com/blog/musicxml-vs-midi-vs-pdf</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/musicxml-vs-midi-vs-pdf</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Guide</category>
      <description>MusicXML keeps the written score, MIDI keeps the performance, PDF keeps the printed page. What each file stores, which to export, and .mxl vs .musicxml.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>Export MusicXML when someone will keep editing the notation, MIDI when the music goes into a DAW or a synthesizer, and PDF when someone will read or print it. MusicXML stores the written score: spelled notes, key and time signatures, rests, voices and lyrics. MIDI stores a performance (which key, when, how hard), so turning it back into notation means guessing. A PDF stores the finished page and nothing a music program can edit.</p>
</section>

<h2 id="what-is-musicxml">What is MusicXML?</h2>
<p>MusicXML is an open, XML-based file format for sheet music, made so that notation programs can exchange scores and archive them. The latest finished version, MusicXML 4.0, was published on June 1, 2021 by the W3C Music Notation Community Group as a final community group report, which is not the same thing as a formal W3C standard. The group reported work toward a 4.1 release in August 2026.</p>
<p>It describes music the way it is written. A pitch is a letter name, an alteration and an octave, so D♯ and E♭ stay different notes. A note has a duration, counted in divisions of a quarter note, and a written type such as a dotted eighth. Rests, voice numbers, beams, stem directions, key signatures (counted in fifths: positive numbers for sharps, negative for flats), time signatures, lyrics with verse numbers, and optional page and system layout each have their own elements. The format also keeps sound apart from appearance. A tempo can be stored in quarter notes per minute, and a note can carry small attack and release offsets that belong to a performance rather than to the page.</p>
<p>Notation programs use it to trade scores. MuseScore Studio, Dorico and Sibelius all document MusicXML import, and MakeMusic’s MusicXML site counts more than 260 applications that support the format (checked October 3, 2026).</p>

<h2 id="what-is-a-midi-file">What is a MIDI file, and what does it leave out?</h2>
<p>A MIDI file is a timed list of performance events: this key went down at this moment with this velocity, came up at that moment, and should sound with this instrument program. MIDI 1.0 dates from 1983. Its file format, Standard MIDI Files 1.0 (RP-001, last revised in February 1996), exists so that programs can exchange time-stamped MIDI data.</p>
<p>A MIDI file can also carry a few musical facts as meta-events: the tempo (in microseconds per quarter note), a time signature, a key signature given as a number of sharps or flats plus major or minor, and lyrics, usually one event per syllable. The specification asks every file to state its tempo and time signature, and when one doesn’t, programs assume 4/4 at 120 beats per minute. Key signatures and lyrics are optional.</p>
<p>What MIDI can’t say is how to write the notes. A note is a key number from 0 to 127, so number 63 is D♯ and E♭ at once; when the first converter we built turned MIDI into notation, it wrote 63 as E♭ every time, even in E major, where it should be D♯. Durations are only start and stop times, so note values have to be inferred. There are no rests (silence is just a gap), no voices, beams or stems, and no page layout. MuseScore Studio’s handbook says MIDI encodes the notes and instruments “but none of the visual formatting.” There is no audio either: the sound comes from whatever synthesizer plays the file.</p>

<h2 id="musicxml-vs-midi-vs-pdf-comparison">MusicXML vs MIDI vs PDF: what each file keeps</h2>
<p>MusicXML keeps what is written, MIDI keeps what is played, and a PDF keeps how the page looks. PDF is the general-purpose page format, standardized as ISO 32000; PDF 2.0 (ISO 32000-2) was first published in 2017 and revised in 2020, and the Library of Congress’s format register describes it as page-oriented. A score in PDF looks the same on any screen or printer, but to a music program it is a picture of notes.</p>
<div class="table-scroll" role="region" aria-label="What MusicXML, MIDI and PDF files store" tabindex="0">
<table>
<caption>What each file stores</caption>
<thead><tr><th scope="col">Information</th><th scope="col">MusicXML</th><th scope="col">MIDI file</th><th scope="col">PDF</th></tr></thead>
<tbody>
<tr><th scope="row">Pitch spelling (D♯ or E♭)</th><td>Yes: letter, alteration, octave</td><td>No: a key number from 0 to 127</td><td>Printed only</td></tr>
<tr><th scope="row">Key signature</th><td>Yes</td><td>Optional: number of sharps or flats, major or minor</td><td>Printed only</td></tr>
<tr><th scope="row">Time signature</th><td>Yes</td><td>Recommended; 4/4 assumed if missing</td><td>Printed only</td></tr>
<tr><th scope="row">Note values and rests</th><td>Yes</td><td>No: start and stop times only</td><td>Printed only</td></tr>
<tr><th scope="row">Voices, beams, stems</th><td>Yes</td><td>No</td><td>Printed only</td></tr>
<tr><th scope="row">Lyrics</th><td>Yes, with verse numbers</td><td>Optional lyric events, usually one per syllable</td><td>Printed only</td></tr>
<tr><th scope="row">Page layout</th><td>Optional: page size, margins, system and page breaks</td><td>No</td><td>Yes, fixed</td></tr>
<tr><th scope="row">Tempo</th><td>Yes, in quarter notes per minute</td><td>Yes, in microseconds per quarter note; 120 BPM assumed if missing</td><td>Printed marking only</td></tr>
<tr><th scope="row">Exact timing and velocity</th><td>Mostly no: written values, with optional performance offsets and dynamics</td><td>Yes, for every note</td><td>No</td></tr>
<tr><th scope="row">Guitar strings and frets</th><td>Yes: the string and fret of each note, and the strings’ tuning and capo</td><td>No</td><td>Printed only, as TAB</td></tr>
<tr><th scope="row">Instrument sound</th><td>Instrument names and optional MIDI programs; no audio</td><td>Program numbers; no audio</td><td>No</td></tr>
<tr><th scope="row">Where you edit it</th><td>Notation apps</td><td>DAWs and sequencers; notation apps import it by guessing</td><td>Nowhere, as music: re-enter it or recognize it with OMR</td></tr>
</tbody>
</table>
</div>
<p class="note">“Printed only” means a person can read it on the page but software can’t use it as music. Getting notation back out of a PDF takes optical music recognition (OMR), which reads the image and infers the notes, so the result needs proofreading.</p>

<h2 id="which-format-to-export">Which format should you export?</h2>
<p>Choose by what happens to the file next.</p>
<ul>
<li>If someone will edit the notation, send MusicXML. MuseScore Studio (free and open source), Dorico and Sibelius all import it. Send .mxl unless a tool asks for plain .musicxml.</li>
<li>If the music goes into a DAW or drives a synthesizer, send MIDI. It keeps the timing and velocity of every note, which is what production work needs.</li>
<li>If someone will play from it, send a PDF, printed or on a tablet, with separate parts for an ensemble.</li>
<li>If someone only needs to hear it, send an MP3 or WAV rendering; a MIDI file makes no sound of its own.</li>
<li>If you’re archiving, keep the MusicXML for the music and the PDF for the look. That’s our habit, not a rule.</li>
</ul>
<p>Finale users have one more thing to weigh. MakeMusic stopped developing and selling Finale on August 26, 2024. It keeps working where it is installed, barring operating-system changes, and MakeMusic’s technical support for it ended in August 2025. MakeMusic’s own migration advice is to export MusicXML, which Finale v27 writes as MusicXML 4.0, because Dorico can’t open Finale’s .mus or .musx files directly (checked October 3, 2026).</p>
<p>To turn a MIDI file into PDF sheet music, import it into a notation program, clean it up there, and export the PDF. MuseScore Studio opens MIDI files and has an import preference for the shortest note value to use; Dorico Pro’s manual describes its own MIDI import and quantization options. Expect cleanup either way, for the reasons in the section on guesswork below. ScoreStarling now accepts MIDI with Choose a file or a file drop, engraves it into an editable score and exports PDF. It omits drum tracks, uses the first meter, and still needs a visual check. You can also import MusicXML/MXL, ABC or printed sheet music; see <a href="https://scorestarling.com/how-it-works#sheet-music">current input limits</a>.</p>

<h2 id="mxl-vs-musicxml">MXL vs MusicXML: what is the difference?</h2>
<p>Both hold the same MusicXML: .mxl is the compressed package, and .musicxml is the plain text file. The W3C’s MusicXML 4.0 tutorial recommends exactly those two suffixes and points out that a plain .xml suffix doesn’t let programs tell MusicXML apart from any other XML file.</p>
<p>An .mxl file is a zip archive compressed with the DEFLATE algorithm. Its first entry is an uncompressed file named mimetype, and a file at META-INF/container.xml points to the score inside. According to the tutorial, compression shrinks MusicXML to about the size of the equivalent MIDI file, so we’d send .mxl. MuseScore Studio exports compressed MusicXML by default. Choose .musicxml when you want to read, compare or process the XML yourself. The two also have separate media types: <code>application/vnd.recordare.musicxml</code> for .mxl and <code>application/vnd.recordare.musicxml+xml</code> for .musicxml.</p>

<h2 id="midi-to-sheet-music-guesswork">Why MIDI to sheet music takes guesswork</h2>
<p>A MIDI file never says how its notes should be written, so any program that turns MIDI into notation has to decide the key, the spelling, the note values, the voices and where the rests go.</p>
<p>We measured how that goes wrong. On October 2, 2026, we gave Basic Pitch MIDI from 12 guitar recordings in the public GuitarSet dataset to the first converter we had built, on the music21 library. None of the 12 scores had a key signature, every rest was hidden (0 of 1,626 visible), up to five voices were stacked on one staff, and 322 of 2,703 notes that belong to the key were spelled against it.</p>
<p>Switching to MuseScore Studio 4.7.5’s MIDI importer fixed most of that, once we prepared the MIDI: notes snapped to a sixteenth-note grid, and a key estimated from the notes written into every note track. Without that step, MuseScore treats off-grid MIDI as a live performance and guesses the meter, tempo and key again. We also capped its import at two voices per staff and turned tuplets off. With those settings, 10 of 12 key signatures matched the dataset’s annotations (the other two were each a fifth away), 0 of 3,213 notes were spelled against the key, all 988 rests were visible, and no staff held more than two voices. The full before-and-after is in <a href="https://scorestarling.com/blog/why-midi-imports-look-messy">why MIDI imports look messy in MuseScore</a>.</p>

<h2 id="musicxml-import-differences">Why a MusicXML file can look different in another app</h2>
<p>MusicXML carries the music well, but each program applies its own fonts, spacing and defaults, so layout and small details often shift on import. MuseScore Studio’s handbook says notes and instrumentation come across faithfully, yet some cleanup is usually needed to make an imported score look like the original. Its import options can keep the file’s page size, margins, staff size and breaks, though spacing and font differences mean the breaks may not fall in the same places. MakeMusic’s guide to moving from Finale to Dorico says much the same: musical content and basic layout such as page and staff size transfer well, while graphical adjustments to individual items usually don’t.</p>
<p>Our own round-trip check, on October 2, 2026, was small. MuseScore Studio 4.7.5’s command-line converter re-imported a three-note MusicXML sample of ours with identical pitches, positions and lengths, but it moved our transcriber credit into the composer position and dropped its role. We haven’t tested Sibelius or Dorico. Before you send a score on, open it in the app it’s going to and check the credits, the layout and anything you adjusted by hand.</p>

<h2 id="what-scorestarling-exports">What ScoreStarling exports, and why its MIDI and MusicXML differ</h2>
<p>ScoreStarling exports both, and they disagree about timing on purpose: the MIDI keeps the performance, and the MusicXML keeps a version written to be read.</p>
<p>In our October 2 test of the one-instrument option, transcription started as MIDI from Basic Pitch at the supplied tempo. MuseScore Studio wrote the notation from a copy snapped to a sixteenth-note grid, in 4/4, with a key estimated from the notes, visible rests and at most two voices per staff. It also simplifies durations: on the 12 guitar recordings above, it wrote 26% of notes longer than the snapped performance, by up to three sixteenths, and never shorter. A player wants a clean dotted quarter; a producer wants the feel. So playback and the MIDI export keep your timing, while the MusicXML, the PDF and the page images show the written version. A pitch edit changes both files, and when you set a note’s written length, its MIDI note is resized from where it was played. Other tools draw the same line: Klangio offers both quantized and unquantized MIDI downloads for its Sing2Notes app (checked October 3, 2026).</p>
<p>What ScoreStarling lets you download, free or after unlocking:</p>
<div class="table-scroll" role="region" aria-label="ScoreStarling downloads, free and after unlocking" tabindex="0">
<table>
<caption>ScoreStarling downloads, October 8, 2026</caption>
<thead><tr><th scope="col">Access</th><th scope="col">Files</th></tr></thead>
<tbody>
<tr><th scope="row">Free</th><td>PDF, page images, separate parts as PDFs, jianpu PDFs of every staff or the melody only, PDFs with guitar TAB, TAB as text, MP3 and WAV. Until the score is unlocked, its pages and TAB text carry a small footer line, “Made with ScoreStarling · scorestarling.com”.</td></tr>
<tr><th scope="row">Unlocked, 15 credits once per score</th><td>MusicXML (.musicxml and .mxl, with the TAB staff of a part shown as TAB), MIDI, ABC and MEI, and pages without the footer line. A band score paid with credits is already unlocked, and Plus unlocks every score while you have it.</td></tr>
</tbody>
</table>
</div>
<p>The workspace’s Download menu gives MusicXML as .musicxml; .mxl and MEI come through an AI assistant. Transcribing one instrument or voice or solo piano (up to three a day together), importing and editing are free; a band uses credits. Numbered notation is also a read-only view in the workspace and the chat panel; edit in staff notation. <a href="https://scorestarling.com/how-it-works#notation">Jianpu has notation-preservation limits</a>. The MP3 and WAV are General MIDI renderings of the score, not stems separated from your recording. The <a href="https://scorestarling.com/how-it-works#specs">specifications</a> list the details.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://www.w3.org/2021/06/musicxml40/">MusicXML 4.0</a> and the <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> and <a href="https://www.w3.org/2021/06/musicxml40/tutorial/midi-compatible-part/">A MIDI-compatible part</a>, MusicXML 4.0 tutorial — 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 tutorial — W3C Music Notation Community Group</li>
<li>MusicXML 4.0 reference: the <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> and <a href="https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/capo/">capo</a> elements — 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> and the <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> — Library of Congress, 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> and <a href="https://handbook.musescore.org/customization/preferences">Preferences</a> — MuseScore Studio Handbook</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> and <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 manual — 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> and <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>Questions and answers</h2><h3>Does a MIDI file contain any sound?</h3><p>No. It holds instructions: which key to play, when, how hard, and a program number for the instrument. The sound comes from whatever synthesizer or sample library plays the file, which is why the same MIDI file sounds different in different apps.</p><h3>Why does my MIDI file open in 4/4 at 120 BPM?</h3><p>The Standard MIDI Files specification asks every file to state its tempo and time signature, and tells programs to assume 4/4 at 120 beats per minute when a file doesn’t. Set the real tempo and meter before you judge the notation.</p><h3>Can I convert a PDF back into MusicXML?</h3><p>Only with optical music recognition (OMR), which reads the image of the page and infers the notation, so the result needs proofreading. If the original notation file or its MusicXML still exists, start from that instead.</p><h3>Is MusicXML the same as a MuseScore or Sibelius file?</h3><p>No. MuseScore Studio’s .mscz and Sibelius’s .sib are each program’s own format. MusicXML is the exchange format between programs; MuseScore Studio, for example, opens MusicXML, compressed MusicXML and MIDI files alongside its own.</p><h3>Which file should I send to a choir or band?</h3><p>A PDF for reading, with separate parts if each player only needs their own line. Add the MusicXML if someone will transpose, rearrange or edit the parts.</p>]]></content:encoded>
    </item>
    <item>
      <title>Remote MCP server OAuth: what we learned shipping it</title>
      <link>https://scorestarling.com/blog/remote-mcp-server-oauth</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/remote-mcp-server-oauth</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Engineering</category>
      <description>How our hosted MCP server signs people in with OAuth 2.1 and Supabase Auth: the 401 challenge, RFC 9728 metadata, client registration, token audience, refresh.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>A remote MCP server is an OAuth protected resource. Without a token, it answers 401 and points to its RFC 9728 metadata; the client follows that to the authorization server, registers, signs the person in with PKCE and the <code>resource</code> parameter, and returns with a bearer token issued for the server’s URL. We run this in production with Supabase Auth. Three details took the work: a 401 with a scope hint and no error code, a token hook that adds our audience and scope to Supabase’s tokens, and dynamic client registration, since our authorization server doesn’t offer client ID metadata documents.</p>
</section>

<h2 id="mcp-oauth-roles">Who does what when an MCP server uses OAuth?</h2>
<p>The MCP server has the smallest job: it accepts or rejects bearer tokens. The MCP authorization specification (version 2025-11-25) makes it an OAuth 2.1 resource server and the assistant an OAuth client, and lets the authorization server, which signs the person in and issues tokens, be a separate service.</p>
<p>Ours is separate. Supabase Auth’s OAuth 2.1 server issues every token, our website supplies the sign-in and consent page, and the endpoint at <code>https://mcp.scorestarling.com/mcp</code> only verifies tokens. It runs Streamable HTTP without MCP sessions and checks the token on every request. ScoreStarling turns recordings into editable sheet music, and this endpoint is how assistants reach it; the <a href="https://scorestarling.com/mcp">setup guide</a> shows the steps people follow.</p>

<h2 id="mcp-oauth-flow">What happens when an MCP client connects?</h2>
<p>The client starts with only the URL. Two answers from our server, shown as of October 3, 2026, tell it where to sign in; six more steps get it a token. First, a request without a token gets a 401 whose <code>WWW-Authenticate</code> header names the metadata and the scopes to request:</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>Second, the client fetches that metadata. RFC 9728 forms its address by inserting <code>/.well-known/oauth-protected-resource</code> between host and path, and requires <code>resource</code> to be identical to the URL the client is using, or the client must stop. Our Supabase project reference is replaced here:</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>The other six steps:</p>
<ol>
<li><p>The client reads the authorization server’s metadata. Supabase’s issuer has a path, so RFC 8414 puts the well-known segment before it: <code>https://&lt;project-ref&gt;.supabase.co/.well-known/oauth-authorization-server/auth/v1</code>. The client checks that <code>issuer</code> matches exactly and that <code>code_challenge_methods_supported</code> lists <code>S256</code>; the MCP spec says to stop if that field is missing.</p></li>
<li><p>It posts its own metadata to the advertised <code>registration_endpoint</code> (dynamic client registration, RFC 7591) and gets a <code>client_id</code>. Our test client registers as a public client with no secret; Anthropic’s documentation says Claude does too.</p></li>
<li><p>It opens the browser at the authorization endpoint with a PKCE S256 challenge, the scope from the 401 and <code>resource=https://mcp.scorestarling.com/mcp</code> (RFC 8707). Supabase hands the browser to our consent page, which signs the person in if needed and asks them to approve or deny the client.</p></li>
<li><p>It exchanges the code with its PKCE verifier and the same <code>resource</code>, and gets an access token that lasts an hour plus a refresh token.</p></li>
<li><p>It sends <code>Authorization: Bearer …</code> on every MCP request. We check the signature against the issuer’s published keys, the issuer, audience, expiry and write scope, then that the account has not been turned off.</p></li>
<li><p>When the token expires, or we answer 401 with <code>invalid_token</code>, it refreshes, again with <code>resource</code>, and gets a new access token and refresh token.</p></li>
</ol>
<p>Clients must send <code>resource</code> on the authorization and token requests even if the authorization server ignores it. Supabase’s OAuth flow documentation doesn’t mention the parameter (checked October 3, 2026). Supabase accepted it in all our live runs, and the audience in our tokens comes from a hook.</p>

<h2 id="mcp-401-www-authenticate">What should the 401 WWW-Authenticate header contain?</h2>
<p>When no token was sent: where the metadata is, which scopes to ask for, and no error code. RFC 6750 §3.1 says a request without any authentication information should not get an error code. <code>invalid_token</code> is for a token that was sent and failed, and tells the client it may get a new one and retry. The MCP spec adds that the 401 should carry a <code>scope</code> hint, which clients must treat as the scopes to request.</p>
<p>We found our own server getting this wrong while writing our setup guide. In MCP Python SDK 1.30.0, <code>RequireAuthMiddleware</code> answers every unauthenticated request with <code>error="invalid_token"</code> and no scope, so a client that had never sent a token was told its token was invalid. A small ASGI middleware around the MCP route now rewrites only the SDK’s 401s; the SDK still decides who gets in, and its 403 <code>insufficient_scope</code> answer is untouched:</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>The hint names <code>openid email profile</code>, the scopes Supabase can grant, matching <code>scopes_supported</code>. Our deployment check now fails if a no-token challenge carries an error code. The fix reached production on October 2, 2026, within four minutes of merging. Anthropic’s connector docs add two rules for Claude: sign-in starts only from a 401 (it ignores <code>WWW-Authenticate</code> on a 200), and it uses only the first entry in <code>authorization_servers</code>.</p>

<h2 id="supabase-custom-access-token-hook">How do you get an MCP audience and scope into Supabase tokens?</h2>
<p>With a custom access token hook. The MCP spec says a server must accept only tokens issued for it, which in practice means checking <code>aud</code>, and we wanted a write scope of our own; Supabase’s documentation says its OAuth server doesn’t support custom scopes (checked October 3, 2026). The hook is a Postgres function that Supabase Auth runs before issuing a token, and it can rewrite claims. Ours, slightly simplified from our migration:</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>The MCP side checks those claims with PyJWT, then applies two rules of its own:</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> is the MCP URL, plus the old one during a move. The client-ID test keeps the website’s own sign-in out: a browser session token from the same Supabase project has the same issuer and keys but no <code>client_id</code>, so it never gets the MCP audience.</p>
<p>The hook has two costs. It stamps one fixed audience rather than following the client’s <code>resource</code>, which works while one authorization server serves one MCP server. And our consent page can only show the scopes the client asked for, so approving the client is what grants the write scope.</p>
<p class="note">An open report, <a href="https://github.com/supabase/auth/issues/2820">supabase/auth#2820</a> (opened September 20, 2026; no replies by October 3), says Supabase’s authorization-details request returns 400 for public clients, <code>offline_access</code> or <code>resource</code>. None of these reproduced for us; in the Supabase Auth source we reviewed, that 400 appears only for an authorization that is no longer pending, such as one fetched twice.</p>

<h2 id="dcr-vs-cimd">Dynamic client registration or client ID metadata documents?</h2>
<p>Use what your authorization server advertises; with Supabase today, that is dynamic client registration (DCR). The 2025-11-25 MCP spec recommends client ID metadata documents (CIMD) when client and server have never met, and keeps DCR for backwards compatibility. Clients should try a client ID given in advance first, then CIMD, then DCR, and only then ask the person.</p>
<div class="table-scroll" role="region" aria-label="Ways an MCP client gets a client ID" tabindex="0">
<table>
<caption>How an MCP client gets a client ID</caption>
<thead><tr><th scope="col">Approach</th><th scope="col">How the client identifies itself</th><th scope="col">Signal in the server’s metadata</th><th scope="col">Option in Claude’s form</th><th scope="col">Our server, October 3, 2026</th></tr></thead>
<tbody>
<tr><th scope="row">Pre-registration</th><td>A client ID created in advance, entered by hand or built in</td><td>None needed</td><td>Use your own OAuth client</td><td>Not offered</td></tr>
<tr><th scope="row">Client ID metadata document</th><td>Its <code>client_id</code> is an HTTPS URL to a JSON file the client hosts, which the server fetches</td><td><code>client_id_metadata_document_supported</code></td><td>Use Claude’s published identity (recommended)</td><td>Not advertised</td></tr>
<tr><th scope="row">Dynamic client registration</th><td>It posts its metadata to the registration endpoint and gets a <code>client_id</code></td><td><code>registration_endpoint</code></td><td>Register automatically</td><td>Advertised</td></tr>
</tbody>
</table>
</div>
<p>Supabase’s metadata, read again on October 3, 2026, lists a registration endpoint, S256 and the <code>none</code> token-endpoint method, and no CIMD flag; its MCP guide has you switch dynamic registration on in the dashboard.</p>
<p>That decides what people pick in Claude’s custom-connector form. Anthropic’s documentation describes the recommended option, Claude’s published identity, as a CIMD that Anthropic hosts and the server must support. So our guide asks for Sign in now, since none of our tools works without an account, and Register automatically. Anthropic’s developer docs say Claude falls back to DCR when CIMD isn’t advertised (CIMD also needs <code>none</code> in <code>token_endpoint_auth_methods_supported</code>); we haven’t tested the form with its default left in place, so we ask for the explicit choice. The client docs we read on October 2, 2026 describe DCR for ChatGPT developer mode, Cursor, VS Code and Gemini CLI.</p>
<p>DCR has a cost: Anthropic notes that Claude registers a new client on every fresh connection, and Supabase warns that dynamic registration lets any MCP client register with your project. Registering grants nothing on its own, though. A person still has to sign in and approve the client, and the verifier checks the account on every request. Because any client can register under any name, our consent page has shown since October 6, 2026 where approving sends the person, and it warns when an app calls itself ChatGPT or Claude but doesn’t return to that assistant’s address.</p>

<h2 id="mcp-refresh-tokens">Do MCP clients stay signed in after the access token expires?</h2>
<p>Yes. Access tokens last an hour, and clients renew them with refresh tokens. Before relying on that, we read Supabase Auth’s source (<code>master</code> as of September 22, 2026):</p>
<ul>
<li>every code exchange returns a refresh token, with or without <code>offline_access</code>;</li>
<li>a refresh must come from the session’s own client, rotates the refresh token and keeps the <code>client_id</code> claim;</li>
<li>the hook runs on refreshes too (<code>authentication_method</code> is <code>token_refresh</code>), so our audience and scope come back each time.</li>
</ul>
<p>A live run on October 2, 2026 matched: the refresh token rotated, and the renewed access token worked. Anthropic’s docs say Claude refreshes after a 401 and up to five minutes before expiry, and adds <code>offline_access</code> when the authorization server lists it in <code>scopes_supported</code>. Supabase’s metadata lists it, and our check that also requested <code>offline_access</code> passed.</p>
<p>Sessions still end when:</p>
<ul>
<li>the account is turned off, or the user is deleted or banned (in our live run, turning the account off rejected the browser session and both MCP tokens);</li>
<li>an old refresh token is reused after Supabase’s reuse interval, 10 seconds by default, which revokes the whole session;</li>
<li>a time-box, an inactivity timeout or “single session per user” is turned on. Keep the last one off, or a website sign-in ends the assistant’s session;</li>
<li>the person signs out of the website with Supabase’s default <code>logout</code>, which ends every session of the account, the assistant’s included. Ours did until October 6, 2026, the likely reason one ChatGPT connection could no longer renew its token; Sign out now sends <code>scope=local</code> and ends only that browser’s session.</li>
</ul>
<p>Supabase advertises no revocation endpoint (checked October 3, 2026), so turning the account off is how we cut off access.</p>

<h2 id="oauth-issuer-trailing-slash">Why does a trailing slash in the issuer matter?</h2>
<p>Because issuers are compared character for character. RFC 8414 requires the <code>issuer</code> in the metadata to be identical to the one the client used to find it, and RFC 9728 asks the same of <code>resource</code>. To a strict client, <code>https://auth.example.com</code> and <code>https://auth.example.com/</code> are different issuers.</p>
<p>The MCP Python SDK stores the issuer as a pydantic <code>AnyHttpUrl</code>, and pydantic 2.13.5, the version in our lockfile, turns <code>https://auth.example.com</code> into <code>https://auth.example.com/</code>. Metadata built from that names an issuer the authorization server never published, so our server writes its own protected resource metadata with the issuer exactly as configured. Supabase’s issuer ends in a path, which pydantic leaves alone, so this never hit us in production; it would with a bare-origin issuer. Our deployment check fails unless the authorization server’s <code>issuer</code> matches ours exactly.</p>

<h2 id="verified-and-not-verified">What we’ve verified, and what we haven’t</h2>
<p>Our first evidence came from a scripted client that follows the MCP spec. On October 2, 2026, one run against production covered the no-token 401, dynamic registration of a public client, consent and code exchange with <code>resource</code>, a refresh with rotation, <code>tools/list</code> with both tokens, and turning the account off, which rejected both tokens and the browser session. A second run also requested <code>offline_access</code>, and both passed. That run used our earlier address; the move to <code>mcp.scorestarling.com</code> has <a href="https://scorestarling.com/blog/mcp-server-url">its own article</a>.</p>
<p>Real clients have connected since. ChatGPT’s developer-mode app signed in through <code>mcp.scorestarling.com</code> and called a tool on October 3, 2026, and Claude connected and ran tools on October 6. Not verified yet:</p>
<ul>
<li>a refresh after a token has really expired: the scripted refresh ran right after sign-in, and we haven’t traced one from a real client;</li>
<li>Supabase’s hosted session settings, which we haven’t read back.</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization">Authorization, MCP specification version 2025-11-25</a> — Model Context Protocol</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc9728">RFC 9728: OAuth 2.0 Protected Resource Metadata</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc6750#section-3.1">RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage, §3.1</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc8707">RFC 8707: Resource Indicators for OAuth 2.0</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc7591">RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc8414">RFC 8414: OAuth 2.0 Authorization Server Metadata</a> — IETF</li>
<li><a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/">OAuth Client ID Metadata Document (Internet-Draft)</a> — IETF OAuth Working Group</li>
<li><a href="https://claude.com/docs/connectors/building/authentication">Authentication for connectors</a> — Anthropic</li>
<li><a href="https://claude.com/docs/connectors/custom/remote-mcp">Add a connector that isn’t in the directory</a> — Anthropic</li>
<li><a href="https://supabase.com/docs/guides/auth/oauth-server/oauth-flows">OAuth 2.1 Flows</a> — Supabase</li>
<li><a href="https://supabase.com/docs/guides/auth/auth-hooks/custom-access-token-hook">Custom Access Token Hook</a> — Supabase</li>
<li><a href="https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication">Model Context Protocol (MCP) Authentication</a> — Supabase</li>
<li><a href="https://supabase.com/docs/guides/auth/sessions">User sessions</a> — Supabase</li>
<li><a href="https://github.com/supabase/auth/issues/2820">supabase/auth issue #2820</a> — GitHub</li>
</ol>
</section><h2>Questions and answers</h2><h3>Does a remote MCP server need its own authorization server?</h3><p>No. The MCP authorization spec lets the authorization server be a separate service. The MCP server publishes RFC 9728 metadata that names it, then validates the tokens it issues, including their audience. Ours points to Supabase Auth and only verifies tokens.</p><h3>Should an MCP server’s 401 include the invalid_token error when no token was sent?</h3><p>No. RFC 6750 §3.1 says a request without credentials should get no error code. Send <code>resource_metadata</code> and a <code>scope</code> hint instead, and keep <code>invalid_token</code> for a token that was sent and rejected, where it tells the client to refresh.</p><h3>Can Supabase Auth issue custom scopes for an MCP server?</h3><p>Not as of October 3, 2026: Supabase’s OAuth 2.1 documentation says custom scopes aren’t supported. We request <code>openid email profile</code> and add our own scope and the MCP audience in a custom access token hook, which also runs on every refresh.</p><h3>What should people choose in Claude’s custom connector form if the server doesn’t support CIMD?</h3><p>Register automatically under OAuth client, which uses dynamic client registration, and Sign in now under Authentication if every tool needs an account. Claude’s recommended published identity is a client ID metadata document, and the server must support it. Our <a href="https://scorestarling.com/mcp#claude">Claude steps</a> show both choices.</p><h3>Do I need the offline_access scope to get refresh tokens from Supabase?</h3><p>Not in our tests. Supabase returned a refresh token on every code exchange without it, in its source and in live runs on October 2, 2026. Some clients ask for it anyway; Anthropic’s docs say Claude does when the authorization server lists it, and our check with it passed too.</p>]]></content:encoded>
    </item>
    <item>
      <title>Where should an MCP server live? We checked 33 addresses</title>
      <link>https://scorestarling.com/blog/mcp-server-url</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/mcp-server-url</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Engineering</category>
      <description>We probed 33 public MCP endpoints on October 2, 2026. 14 of the 22 services that asked for sign-in used mcp.&lt;domain&gt;/mcp. How we chose ours and moved it.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>Most remote MCP servers we checked have a host of their own. On October 2, 2026, 14 of the 22 services whose endpoint asked for sign-in used <code>mcp.&lt;domain&gt;/mcp</code>, six more used an <code>mcp.</code> host with no path or a versioned one, and two used an <code>api.</code> host. No address is standard and paths can’t be guessed, so publish the exact URL. We moved ours from <code>scorestarling.com/mcp</code> to <code>https://mcp.scorestarling.com/mcp</code> so that the website’s <code>/mcp</code> could become the setup guide for people.</p>
</section>

<h2 id="mcp-server-url-survey">What URLs do public MCP servers use?</h2>
<p>Mostly a dedicated <code>mcp.</code> subdomain, usually with <code>/mcp</code> as the path. We went looking while choosing between <code>mcp.scorestarling.com/mcp</code> and <code>api.scorestarling.com/v1/mcp</code>, the form ElevenLabs uses, because we wanted to know what other services actually do.</p>
<p>On October 2, 2026, between 17:40 and 17:42 UTC, we sent one unauthenticated POST to each of 33 addresses from 27 services, with <code>{}</code> as the body, an <code>Accept: application/json, text/event-stream</code> header and an 8-second timeout, and recorded only the status code. A 401 means something at that address answered and wants a token, which is how a protected MCP server starts sign-in. A 404 means nothing is served at that path. Some addresses were published by the services; others were variants and guesses we tried to see which paths answer. It’s a snapshot of well-known services, not a census, and any of these may have changed since.</p>
<div class="table-scroll" role="region" aria-label="Address patterns of MCP servers that asked for sign-in" tabindex="0">
<table>
<caption>Address patterns of the 22 services that answered 401, October 2, 2026</caption>
<thead><tr><th scope="col">Pattern</th><th scope="col" class="num">Services</th><th scope="col">Addresses that answered 401</th></tr></thead>
<tbody>
<tr><th scope="row">An mcp. subdomain with the path /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">An mcp. subdomain with no path or a versioned path</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’s <code>mcp.asana.com/sse</code> also answered 401)</td></tr>
<tr><th scope="row">An api. subdomain with a path</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">A path on the main or docs site</th><td class="num">0</td><td>None; <code>huggingface.co/mcp</code> and <code>learn.microsoft.com/api/mcp</code> answered 400</td></tr>
<tr><th scope="row">Total</th><td class="num">22</td><td>23 addresses, because Asana answered at two</td></tr>
</tbody>
</table>
</div>
<div class="table-scroll" role="region" aria-label="MCP addresses that did not answer 401" tabindex="0">
<table>
<caption>The 10 addresses that didn’t answer 401</caption>
<thead><tr><th scope="col">Result</th><th scope="col">Addresses</th><th scope="col">What it tells us</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>No endpoint at that path; each service answered 401 at another path on the same host</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>Something answered but rejected our empty request; we didn’t look further</td></tr>
<tr><th scope="row">No response</th><td><code>mcp.suno.com/mcp</code>, <code>mcp.elevenlabs.io/mcp</code></td><td>No HTTP answer within 8 seconds</td></tr>
</tbody>
</table>
</div>
<p>The 404s carry the practical lesson: an MCP URL can’t be derived from a domain. Stripe and Vercel answer at the bare host and return 404 at <code>/mcp</code>; Higgsfield is the other way round; ElevenLabs needs its <code>/v1</code>. A client uses exactly the URL it is given, and RFC 9728 makes that URL the identity the server’s metadata must match, so the same string has to appear in your docs, install links and plugin manifests. Manifests show still more variety: among the plugins we had installed were <code>gitlab.com/api/v4/mcp</code>, a path on gitlab.com itself, and <code>mcp.hubspot.com/anthropic</code>. We didn’t probe those two.</p>

<h2 id="mcp-endpoint-separate-host">Should the MCP endpoint share a host with your website?</h2>
<p>We don’t think so. The endpoint is for software and the setup page is for people, and both want the obvious address. Until October 2, 2026, <code>scorestarling.com/mcp</code> was our MCP server, so the guide had to live somewhere else; and since that URL’s 401 is how every client discovers sign-in, it couldn’t also be a web page.</p>
<p>We considered serving both at one URL, a page to browsers and MCP to clients, and rejected it as a permanent setup: one wrong guess about a request breaks sign-in discovery for every client. The transport spec also lets clients send GET to the endpoint to open an event stream, and expects either that stream or a 405 in reply, not an HTML page.</p>
<p>A separate host also keeps website changes away from the endpoint: a redesign can’t move it, and a CDN cache or browser challenge that suits a marketing site never sits in front of it. Assistants call the server from their own infrastructure, not from a browser: Anthropic’s connector docs say Claude connects from Anthropic’s cloud, and that a firewall in front of the identity provider can break sign-in. Two peers we looked at split the same way (checked October 2 and again October 3, 2026): Higgsfield’s guide is at <code>higgsfield.ai/mcp</code> and its server at <code>mcp.higgsfield.ai/mcp</code>; ElevenLabs’ guide is at <code>elevenlabs.io/mcp</code> and its server at <code>api.elevenlabs.io/v1/mcp</code>.</p>
<p>Our MCP host serves only <code>/mcp</code> and <code>/.well-known/*</code>. Every other path gets a 308 to the same path and query on the website, so sign-in, consent, pages, download links and cookies stay on one host. The website’s <a href="https://scorestarling.com/mcp">/mcp</a> is now the setup guide for Claude, ChatGPT and other clients.</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, a bare host, or api.example.com/v1/mcp?</h2>
<p>Any of them works; choose by what else lives on the host. The MCP authorization spec lists <code>https://mcp.example.com/mcp</code> and <code>https://mcp.example.com</code> among valid canonical server URIs and asks for the form without a trailing slash unless the slash matters. The transport spec only requires one endpoint path that accepts POST and GET, with <code>https://example.com/mcp</code> as its example, and Anthropic’s connector docs use <code>https://mcp.example.com/mcp</code>.</p>
<div class="table-scroll" role="region" aria-label="Address options we weighed" tabindex="0">
<table>
<caption>The options we weighed for ScoreStarling</caption>
<thead><tr><th scope="col">Option</th><th scope="col">Seen at, October 2, 2026</th><th scope="col">For us</th></tr></thead>
<tbody>
<tr><th scope="row">An mcp. host with /mcp (chosen)</th><td>14 services, including Notion, Linear, Canva, Higgsfield and Runway</td><td>The most common pattern in our sample; our server already answered at <code>/mcp</code>, so only the host changed</td></tr>
<tr><th scope="row">A bare mcp. host</th><td>Stripe, Vercel, Box, Miro</td><td>The shortest, but it needed more code changes and another release first</td></tr>
<tr><th scope="row">An api. host with /v1/mcp</th><td>ElevenLabs; GitHub uses <code>api.githubcopilot.com/mcp/</code></td><td>Suits a company with a public developer API on that host; we don’t have one</td></tr>
</tbody>
</table>
</div>
<p>Two smaller rules. Don’t end a Streamable HTTP endpoint in <code>/sse</code>: Anthropic’s docs say a URL ending that way selects the older SSE transport in Claude’s connector form. And leave off the trailing slash unless you mean it; of the 23 addresses that answered 401, only GitHub’s was written with one.</p>

<h2 id="move-mcp-server-url">How do you move an MCP server to a new URL?</h2>
<p>Serve both addresses for a while, and make each one complete on its own: its own 401, its own metadata, and tokens for either audience. We moved in four steps, all live within an hour on the evening of October 2, 2026 (UTC):</p>
<ol>
<li><p>Ship alias support while nothing uses it. One setting lists earlier addresses, each HTTPS and ending in <code>/mcp</code>. A token verifies if its audience is the current address or an alias; any other audience is refused. The host allow-lists (Starlette’s trusted hosts and the MCP SDK’s DNS-rebinding protection) hold every address plus the website. With no alias set, production behaved as before.</p></li>
<li><p>Add the host, then switch the address and set the alias in one change. The new subdomain got its DNS records, the certificate followed about six minutes later, and the host answered 400 until our server was told to trust it. One configuration change set the new resource URL and listed the old one as an alias, so a single redeploy picked up both. Doing this before step 1 was live would have made the running code refuse the website’s own host.</p></li>
<li><p>Repoint everything people copy: the setup guide, including the encoded one-click install links for Claude, Cursor and VS Code, the plugin manifests and the docs.</p></li>
<li><p>Switch the token audience, then retire the old address. A migration made our access-token hook issue the new audience, and CI applied it before the code shipped; while the alias stood, tokens with either audience still worked. Then we removed the alias and redeployed. For the few minutes between that release and the redeploy, the website’s <code>/mcp</code> sent browsers the guide and MCP clients the server. It now serves only the guide, and the guide’s old address, <code>/connect</code>, answers with a 308 to it.</p></li>
</ol>
<p>The metadata is the easy part to miss. RFC 9728 requires its <code>resource</code> to be identical to the URL the client connected to, and Anthropic’s docs require it to equal the URL as typed into Claude. So during the overlap each host described itself, and each 401 pointed to its own host’s document:</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>We didn’t redirect <code>/mcp</code> itself. The transport spec doesn’t describe redirects for the endpoint, and its URL is part of the OAuth handshake as the resource and the token audience, so we served MCP at both addresses instead. For every other path, a 308 rather than a 301 keeps the method, since RFC 9110 doesn’t let a client turn a POST into a GET after a 308. How the audience gets into our tokens is in <a href="https://scorestarling.com/blog/remote-mcp-server-oauth">our notes on MCP OAuth</a>.</p>
<p>We could move that fast because ScoreStarling was still a small invite-only pilot, and nobody relied on the old address yet. Once the alias is gone, a client still configured with <code>scorestarling.com/mcp</code> has to reconnect. Our runbook expects a client already on the new address but holding an old-audience token to get one 401 <code>invalid_token</code>, refresh, and receive the new audience; we haven’t watched a real Claude or ChatGPT client do that. With real users, keep the alias until traffic to the old address stops.</p>

<h2 id="what-broke">What broke during the move?</h2>
<p>Three small things went wrong along the way:</p>
<ul>
<li>A check script built the metadata URL with <code>resource.replace('/mcp', '/.well-known/oauth-protected-resource/mcp')</code>. On the old address that worked. On <code>https://mcp.scorestarling.com/mcp</code>, <code>/mcp</code> also matches inside <code>//mcp.</code>, and the result was <code>https://.well-known/oauth-protected-resource/mcp.scorestarling.com/.well-known/oauth-protected-resource/mcp</code>. Build the URL from its parsed parts instead: scheme and host, then the well-known segment, then the path.</li>
<li>The longer address overflowed our guide on a 320-pixel-wide screen until inline code was allowed to wrap.</li>
<li>Deleting the alias variable with Railway’s command-line tool only staged the change; the old address stayed accepted until we redeployed by hand.</li>
</ul>

<h2 id="mcp-url-checklist">A checklist for choosing an MCP server address</h2>
<ul>
<li>Give the endpoint a host that serves nothing for people; <code>mcp.</code> plus your domain is the common choice in our sample.</li>
<li>Pick a path once. <code>/mcp</code> is the most common and a bare host is also valid; avoid a trailing slash and a path ending in <code>/sse</code>.</li>
<li>Keep the setup page for people on your main site, at an address they can guess, and link it from your navigation.</li>
<li>Serve RFC 9728 metadata for the exact URL, at <code>/.well-known/oauth-protected-resource</code> plus your path and at the root, with <code>resource</code> equal to the address the client used.</li>
<li>Make that URL the token audience, and build alias support before you need to move.</li>
<li>Redirect every other path on the MCP host to your website with a 308, keeping the query string.</li>
<li>Keep browser challenges and HTML caching away from the MCP host.</li>
<li>Publish one string everywhere (guide, install links, plugin manifests, docs) and test that every copy matches.</li>
<li>To move: aliases first, then the new address and the alias in one change, every copy repointed, the audience switched, and the old address retired after its traffic stops.</li>
</ul>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/transports">Transports, MCP specification version 2025-11-25</a> — Model Context Protocol</li>
<li><a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization">Authorization, MCP specification version 2025-11-25</a> — Model Context Protocol</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc9728">RFC 9728: OAuth 2.0 Protected Resource Metadata</a> — IETF</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.9">RFC 9110: HTTP Semantics, §15.4.9, 308 Permanent Redirect</a> — IETF</li>
<li><a href="https://claude.com/docs/connectors/custom/remote-mcp">Add a connector that isn’t in the directory</a> — Anthropic</li>
<li><a href="https://claude.com/docs/connectors/building/authentication">Authentication for connectors</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>Questions and answers</h2><h3>Does an MCP server URL have to end in /mcp?</h3><p>No. The MCP spec requires a single endpoint path and lists both <code>https://mcp.example.com</code> and <code>https://mcp.example.com/mcp</code> as valid server URIs. In our October 2, 2026 sample, 18 of the 22 services that asked for sign-in used a path such as <code>/mcp</code> or <code>/v1/mcp</code>, and 4 answered at the bare host. Pick one and publish it exactly.</p><h3>Can the setup page and the MCP endpoint share one URL?</h3><p>You can send browsers a page and MCP clients the server at one URL, but we decided against it apart from a few minutes during our move. One misjudged request breaks sign-in discovery, and Streamable HTTP clients may send GET to the endpoint expecting an event stream or a 405. Our guide is at scorestarling.com/mcp and the server has its own host.</p><h3>Will changing an MCP server URL disconnect users?</h3><p>Only when you retire the old address. The URL is the OAuth resource and the token audience, so a client configured with the old one has to reconnect once it stops answering. Serve both for a while: accept both audiences, give each address its own metadata, and retire the old one after its traffic stops.</p><h3>What does a 401, 404 or 400 mean when I POST to an MCP URL?</h3><p>In our survey, a 401 meant a protected endpoint answered and asked for a token, and a 404 meant nothing was served at that path. A 400 only says something rejected our empty test body; we didn’t interpret those further. To test a server properly, connect with a real MCP client.</p><h3>Should an MCP server URL end with a slash?</h3><p>Preferably not. The MCP authorization spec asks implementations to use the form without a trailing slash unless the slash is significant. Of the 23 addresses that answered 401 in our survey, only GitHub’s, <code>api.githubcopilot.com/mcp/</code>, was written with one.</p>]]></content:encoded>
    </item>
    <item>
      <title>Mozart’s starling: the bird behind our name</title>
      <link>https://scorestarling.com/blog/mozart-starling</link>
      <guid isPermaLink="true">https://scorestarling.com/blog/mozart-starling</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
      <dc:creator>Haiyi Mei</dc:creator>
      <category>Story</category>
      <description>On May 27, 1784, Mozart paid 34 kreuzer for a starling that sang his new K. 453 finale theme, adding a pause and a G sharp. We checked the record.</description>
      <content:encoded><![CDATA[<section class="answer" aria-labelledby="short-answer">
<h2 id="short-answer">The short answer</h2>
<p>On May 27, 1784, Mozart paid 34 kreuzer for a starling that could sing the opening of the finale of his Piano Concerto No. 17 in G major, K. 453, which he had entered in his catalogue of works about six weeks earlier. In his account book he wrote out the bird’s version, which pauses on one note and sings G sharp where the concerto has G, and added “Das war schön!” (“That was beautiful!”). The bird lived with him for three years; when it died on June 4, 1787, he buried it in the garden and wrote it a comic poem.</p>
</section>

<h2 id="the-1784-purchase">The 1784 purchase and the account-book entry</h2>
<p>Mozart bought the starling in Vienna on May 27, 1784, and the first record of the bird is the entry he made that day in his account book, also called his expense book. He had started it that year, at the same time as the catalogue of his works. According to Wikipedia, which cites Hermann Abert’s biography, he kept the expense book for only about a year, while the catalogue ran until his death in 1791.</p>
<p>The Neue Mozart-Ausgabe (NMA), the scholarly edition of Mozart’s complete works that the Mozarteum Foundation publishes online, reproduces the entry in the preface to the volume that contains K. 453. It is short. Across the top run the date, “Vogel Stahrl” (starling bird) and “34 Kr.” Below is a single staff of music in G major and cut time. Under the staff Mozart wrote “Das war schön!”</p>
<p>“Kr.” stands for kreuzer, a coin of the day; sixty made a gulden. The NMA’s English translation prints “34 Crowns” and “That was nice!”, while Wikipedia, which cites Otto Erich Deutsch’s documentary biography for the German text, reads kreuzer and gives “That was beautiful!”, as our homepage does. The editors of that NMA volume, Eva and Paul Badura-Skoda, say Mozart had apparently enjoyed hearing the bird whistle the theme of the concerto’s last movement and bought it for that reason, “even if the intonation may not have been quite clean.”</p>

<h2 id="the-tune-bar-by-bar">The tune: the finale theme of K. 453, bar by bar</h2>
<p>The bird sang the first phrase of the theme that opens the concerto’s finale, an upbeat and four bars, with two changes: it paused on the G at the end of the first full bar, and it sang G sharp instead of G in the next bar. Mozart’s entry keeps both.</p>
<p>Mozart wrote the concerto for his pupil Barbara (Babette) Ployer and entered it in his catalogue on April 12, 1784. Its finale, an Allegretto in cut time, is a set of variations on a plain, singable theme and ends with a Presto. The table sets the start of that theme beside the line in the account book.</p>
<div class="table-scroll" role="region" aria-label="The K. 453 finale theme beside the starling’s version" tabindex="0">
<table>
<caption>Opening phrase of the K. 453 finale theme and the starling’s version, by bar (pitches only)</caption>
<thead><tr><th scope="col">Bar</th><th scope="col">Concerto, K. 453 finale</th><th scope="col">Starling, as Mozart wrote it down</th><th scope="col">Difference</th></tr></thead>
<tbody>
<tr><th scope="row">Upbeat</th><td>B C (two eighth notes)</td><td>B C</td><td>None</td></tr>
<tr><th scope="row">1</th><td>D D D G</td><td>D D D G, with a fermata (pause) on the G</td><td>The bird holds the G</td></tr>
<tr><th scope="row">2</th><td>F♯ F♯ G G</td><td>F♯ F♯ G♯ G♯</td><td>G sharp instead of G</td></tr>
<tr><th scope="row">3</th><td>A A, then eighths A B C A</td><td>The same</td><td>None</td></tr>
<tr><th scope="row">4</th><td>B (half note), then the theme goes on</td><td>B (half note); the entry ends</td><td>None</td></tr>
</tbody>
</table>
</div>
<p class="note">Pitches follow Wikipedia’s transcription of the entry, which cites Deutsch, and its corrected version of the tune, which follows the musicologist Daniel Heartz. We checked both against the NMA: its reproduction of the entry shows the fermata and the sharp, and its score of the finale (page 46 of the volume) has neither. The concerto also has grace notes, staccato dots and a slur that the account-book line leaves out.</p>
<p>Read as a transcription, the entry is careful work. Mozart knew exactly how his tune went, and he still wrote the pause and the sharp where he heard them instead of tidying them away. The line records the performance, not the composition.</p>

<h2 id="how-the-bird-knew-the-tune">How did the bird know a six-week-old tune?</h2>
<p>Nobody knows. The documents record the purchase, not how the bird learned the tune, and the main accounts tell the story in different orders:</p>
<ul>
<li>The NMA preface says Mozart heard the bird whistle the theme and bought it because of that.</li>
<li>Wikipedia’s article on the common starling, citing the book <cite>Birds Britannica</cite> (2005), says he bought it from a shop after hearing it sing a phrase from a work he had written six weeks earlier and that had not yet been performed in public.</li>
<li>Wikipedia’s article on Mozart’s starling suggests the reverse order: presumably Mozart taught the bird the tune in the shop, or wherever he bought it.</li>
</ul>
<p>“Not yet performed in public” is uncertain too. In a letter of June 12, 1784, quoted in the NMA preface, Mozart tells his father that Babette Ployer will play her new concerto in G the next day at a musical evening at the Ployers’ house in Döbling. The musicologist Michael Lorenz has argued that Mozart may have played it earlier, at his concert with the violinist Regina Strinasacchi at the Kärntnertortheater on April 29, 1784, as Wikipedia’s article on the concerto summarizes him. If so, the tune had been heard in public four weeks before Mozart bought the bird.</p>
<p>Starlings can learn quickly: Wikipedia’s starling article notes that a wild starling has been seen to copy a sound it heard only once. To us, the simplest explanation is a composer whistling his newest tune in a bird shop. Nothing in the documents shows that it happened.</p>

<h2 id="why-starlings-mimic">Why starlings are such good mimics</h2>
<p>Common starlings (<em>Sturnus vulgaris</em>) are vocal learners that build borrowed sounds into their own song, so a pet starling picking up a whistled melody is ordinary behavior for the species. A male’s song bout can run a minute or more: pure whistles, then variable sequences that take in snatches of other birds’ songs and other noises, then clicks and a final burst of high song. Practiced birds have up to 35 variable song types, according to Chris Feare’s 1996 account of the species in <cite>British Birds</cite>, which Wikipedia cites.</p>
<p>In their essay on vocal copying, Paul Ehrlich, David Dobkin and Darryl Wheye name the European starling as a species whose vocal mimicry is well known. Their explanation is repertoire size: a large, varied song can help a male attract a mate and intimidate rivals, and borrowing from the surroundings, even dog barks, machinery or human whistling, is one way to grow it. They add that mimics usually give themselves away through their own tone, timing or context, so listeners are rarely fooled.</p>
<p>Pet starlings pick up human phrases readily. Konrad Lorenz, as cited on Wikipedia, noted that since the words mean nothing to the bird, it mixes them up and uses them at odd moments. Meredith West and Andrew King, two researchers with long experience of pet starlings, linked the birds’ mimicry to their lively social interactions.</p>
<p>Their anatomy helps. A 2025 study in <cite>Scientific Reports</cite> analyzed 115 videos, posted online, of birds imitating the robot R2-D2 from <cite>Star Wars</cite>; eight were of starlings. The starlings copied the robot’s complex, multi-tone sounds more accurately than the parrots did, and the two groups did equally well on simple beeps. The authors put the difference down to the syrinx, the bird’s voice organ: a starling controls its two sides independently, so it can make two sounds at once, and a parrot can’t.</p>
<p>Starlings also seem to hear tunes differently from us. In a 2016 study in <cite>PNAS</cite>, five starlings learned to tell apart four-tone sequences that rose or fell in pitch. When the sequences were shifted slightly up or down, or played with a different timbre, the birds did no better than chance. They recognized versions that kept each tone’s spectral shape, meaning how its energy is spread across frequencies, far more readily, even with the pitch removed. A musician hears a melody as intervals that survive transposition; a starling seems to hear something closer to a string of sound shapes. That may be part of why a starling’s copy of a tune comes out close but not exact. This is our reading, not something anyone measured on Mozart’s bird.</p>

<h2 id="the-funeral-and-the-poem">June 1787: the starling’s funeral and Mozart’s poem</h2>
<p>The starling died on June 4, 1787, about three years after Mozart bought it, and he buried it in the garden with some ceremony and a poem he wrote for it. It was a hard season. His father, Leopold Mozart, had died in Salzburg on May 28, a week earlier.</p>
<p>The funeral reaches us secondhand, mostly through Mozart’s early biographers, and the accounts differ:</p>
<ul>
<li>Franz Xaver Niemetschek, who interviewed Mozart’s widow, Constanze, mentions a much-loved starling that Mozart gave a proper gravestone, with an inscription, in the garden he rented.</li>
<li>Georg Nikolaus Nissen, Constanze’s second husband, tells it the same way in his 1828 biography: on the death of a beloved starling Mozart wrote verses and put up a proper grave with an epitaph in his rented garden (page 668), and he loved animals, especially birds (page 670). The procession comes from elsewhere: Nissen’s collected notes, which Rudolf von Lewicki printed in the <cite>Mozarteums-Mitteilungen</cite> in November 1919. There Nissen writes that Mozart loved dogs and birds and always kept some, that his dog was called Puzzipaukel, and that when a bird died he arranged a funeral procession in which everyone who could sing had to follow in great veils, with a sort of requiem and an epitaph in verse (page 29). The note says “a bird” without naming the starling; Otto Erich Deutsch quotes it in English on page 225 of his documentary biography.</li>
<li>West and King’s 1990 article in <cite>American Scientist</cite>, as summarized on RILM’s blog, has veiled mourners marching in procession, singing hymns and hearing the poem recited at the graveside.</li>
</ul>
<p>The poem is dated June 4, 1787. It opens “Hier ruht ein lieber Narr, / Ein Vogel Staar” (“Here rests a dear fool, a starling”; our translation). It mourns a bird taken in his best years, insists he was not bad, only rather lively and at times a lovable rascal, and ends with a joke about its own author: dying, the bird spared no thought for the man “der so schön reimen kann,” who rhymes so beautifully. It is the same word, schön, that Mozart had written under the bird’s tune three years before.</p>
<p>Robert Spaethling, who translated the poem into English, reads it as funny and bittersweet, written while Mozart was grieving and deep in work on <cite>Don Giovanni</cite>. The folklorist Wolfgang Mieder has suggested that Mozart was partly writing about himself. West and King point out how closely pet starlings bond with their keepers, which makes the grief behind the comedy easy to believe.</p>

<h2 id="did-the-starling-influence-mozart">Did the starling influence Mozart’s music?</h2>
<p>It did not shape the concerto, which Mozart had finished six weeks before he bought the bird, and the idea that it shaped a later piece, <cite>A Musical Joke</cite> (<cite lang="de">Ein musikalischer Spaß</cite>, K. 522), is a hypothesis with no document behind it. Lyanda Lynn Haupt, a naturalist and the author of the 2017 book <cite>Mozart’s Starling</cite>, told NPR that the dates rule out the theory that the bird inspired the concerto.</p>
<p>The case for the later piece was made by West and King. In their 1990 article they wrote that many elements of <cite>A Musical Joke</cite> “bear the vocal autograph of a starling.” The timing is suggestive: Mozart entered the piece in his catalogue on June 14, 1787, ten days after the bird died. Haupt made a similar case to NPR; laid over starling song, the piece’s oddest passages show, in her words, the same “fractured phrases” and playfulness.</p>
<p>Treat it as a hypothesis. Wikipedia notes that West and King based the idea on the description of the piece in a record sleeve note. Many commentators hear <cite>A Musical Joke</cite> as a parody of clumsy composers, and Mozart is not known to have said what he meant by it. None of the sources we read cites a letter or note of his that connects the piece to the bird. The only documented musical link between Mozart and his starling that we know of is the account-book entry of May 27, 1784.</p>

<h2 id="why-we-named-scorestarling-after-it">Why we named ScoreStarling after it</h2>
<p>We named the product after the bird because its story is the job we do. In one line: a bird sang, and a composer wrote down what he heard. The name is Score plus Starling, and our logo is a starling whose raised wing forms the flag of a note. The less famous detail matters to us as much. Mozart’s line is a faithful record of what he heard, and what he heard was not quite what he had written. A transcription is a first draft, and you finish it by ear.</p>
<p>ScoreStarling listens to a recording and writes it out as MIDI and an editable score. Play it beside the recording, loop a passage, correct the notes and export. It also reads printed sheet music, score files and ABC or MusicXML text, and shows the score on a staff, as guitar TAB or as jianpu. Transcribing one instrument, a voice or solo piano and editing are free; a band and unlocking a score’s MusicXML and MIDI use credits. Every account gets 100 free credits a month, and the <a href="https://scorestarling.com/how-it-works#specs">specifications</a> have the rest. Our <a href="https://scorestarling.com/blog/ai-music-transcription-accuracy">accuracy article</a> says what we measured and on which recordings.</p>

<section class="sources" aria-labelledby="sources">
<h2 id="sources">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, preface by Eva and Paul Badura-Skoda (1965), English translation</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: Concerto in G, K. 453, third movement, score page 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> — Köchel Catalogue online, Internationale Stiftung Mozarteum</li>
<li><a href="https://en.wikipedia.org/wiki/Mozart%27s_starling">Mozart’s starling</a> — Wikipedia (citing Deutsch 1965, Heartz 2009, Abert 2016, West and King 1990, Spaethling 2000, Mieder 2003 and Niemetschek)</li>
<li><a href="https://en.wikipedia.org/wiki/Piano_Concerto_No._17_(Mozart)">Piano Concerto No. 17 (Mozart)</a> — Wikipedia (citing Lorenz 2006)</li>
<li><a href="https://en.wikipedia.org/wiki/Common_starling">Common starling</a> — Wikipedia (citing Feare 1996 in <cite>British Birds</cite>, Cocker and Mabey 2005, Lorenz 1961)</li>
<li><a href="https://en.wikipedia.org/wiki/A_Musical_Joke">A Musical Joke</a> — Wikipedia</li>
<li><a href="https://en.wikipedia.org/wiki/Leopold_Mozart">Leopold Mozart</a> — Wikipedia</li>
<li><a href="https://en.wikipedia.org/wiki/Kreuzer">Kreuzer</a> — Wikipedia</li>
<li><a href="https://digibib.mozarteum.at/ismretroverbund/periodical/pageview/2498147">Aus Nissens Kollektaneen</a> — Rudolf von Lewicki, <cite>Mozarteums-Mitteilungen</cite> 2, no. 1, Salzburg, November 1919, pages 28–30 (scan at Bibliotheca Mozartiana digital, Mozarteum Foundation)</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 edition, page 225 (quoting Nissen’s notes as printed by Rudolf von Lewicki, <cite>Mozarteums-Mitteilungen</cite>, November 1919)</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, pages 668 and 670 (scan at the Internet Archive)</li>
<li><a href="https://bibliolore.org/2017/09/14/mozarts-starling/">Mozart’s starling</a> — Bibliolore, the RILM blog (2017), summarizing West and King, “Mozart’s Starling,” <cite>American Scientist</cite> 78, no. 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 (April 20, 2017)</li>
<li><a href="https://web.stanford.edu/group/stanfordbirds/text/uessays/uVocal_Copying.html">Vocal Copying</a> — Paul R. Ehrlich, David S. Dobkin and 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 and 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 and Timothy Q. Gentner, <cite>PNAS</cite> 113 (2016)</li>
</ol>
</section><h2>Questions and answers</h2><h3>What piece did Mozart’s starling sing?</h3><p>The opening phrase of the theme of the finale (Allegretto) of Mozart’s Piano Concerto No. 17 in G major, K. 453. Mozart had entered the concerto in his catalogue on April 12, 1784. When he bought the bird on May 27, he wrote its version in his account book: the same upbeat and four bars, with a pause on the G at the end of bar 1 and G sharp in place of G in bar 2.</p><h3>What does “Das war schön” mean in Mozart’s notebook?</h3><p>It means “That was beautiful,” or “That was lovely”; the English translation of the Neue Mozart-Ausgabe preface has “That was nice!” Mozart wrote it under the starling’s version of his tune, in the account book where he recorded the bird (“Vogel Stahrl”) and its price, 34 kreuzer, on May 27, 1784.</p><h3>What happened to Mozart’s starling?</h3><p>It lived in Mozart’s household for about three years and died on June 4, 1787, a week after his father, Leopold. Mozart buried it in his garden and wrote a comic poem for it, dated that day, that begins “Hier ruht ein lieber Narr, / Ein Vogel Staar” (“Here rests a dear fool, a starling”). His early biographer Georg Nikolaus Nissen wrote that Mozart built the beloved starling a proper grave with an epitaph in his rented garden and loved animals, especially birds; the veiled mourners come from his private notes, first printed in 1919, not from that book.</p><h3>Can starlings really copy music?</h3><p>Yes. Common starlings are vocal mimics that work other birds’ songs and other sounds into their own song, and pet starlings pick up words and tunes from the people around them. They copy closely, not perfectly: Mozart’s bird added a pause and sang G sharp for G, and a 2016 lab study found that starlings recognize tone sequences by their spectral shape rather than by pitch.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
