<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: lvyafei2026-coder</title>
    <description>The latest articles on DEV Community by lvyafei2026-coder (@lvyafei2026coder).</description>
    <link>https://dev.to/lvyafei2026coder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4181063%2Fdd63c848-be62-41b1-aeff-3df087962c0f.jpg</url>
      <title>DEV Community: lvyafei2026-coder</title>
      <link>https://dev.to/lvyafei2026coder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lvyafei2026coder"/>
    <language>en</language>
    <item>
      <title>Designing a prompt builder for a text-to-music model: 500 characters, 4 instruments, and a lot of restraint</title>
      <dc:creator>lvyafei2026-coder</dc:creator>
      <pubDate>Tue, 13 Oct 2026 06:05:49 +0000</pubDate>
      <link>https://dev.to/lvyafei2026coder/designing-a-prompt-builder-for-a-text-to-music-model-500-characters-4-instruments-and-a-lot-of-5105</link>
      <guid>https://dev.to/lvyafei2026coder/designing-a-prompt-builder-for-a-text-to-music-model-500-characters-4-instruments-and-a-lot-of-5105</guid>
      <description>&lt;p&gt;&lt;strong&gt;The input box is the product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the only interface to your model is a single text field, output quality is decided before generation ever starts. Feed a text-to-music model &lt;code&gt;sad song&lt;/code&gt; and you get something generic — it has to guess the genre, the tempo, the instrumentation, whether you wanted vocals at all, and how long the track should be.&lt;/p&gt;

&lt;p&gt;We build a text-to-song tool, and the failure mode we kept running into wasn't "the model is bad". It was "the prompt contained no decisions". So we built a prompt builder: a free, static page that turns a few clicks plus one short idea into a prompt the model can act on.&lt;/p&gt;

&lt;p&gt;Here are the constraints we were working with, and the three decisions that followed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraints come first
&lt;/h2&gt;

&lt;p&gt;Before touching the UI, write down what the pipeline actually accepts. Ours:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;required, 5–500 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;10–300 s at API level; 60 s on free tier, 120 s on paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vocals&lt;/td&gt;
&lt;td&gt;lyrics required unless the request is explicitly instrumental&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;mp3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 500-character cap is the one that shapes everything else. It's generous for a &lt;em&gt;specification&lt;/em&gt; and tiny for a &lt;em&gt;wish&lt;/em&gt;. Every control you add to the UI eats budget, so the UI has to make decisions &lt;strong&gt;for&lt;/strong&gt; the user instead of exposing raw parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1: a controlled vocabulary, not free text
&lt;/h2&gt;

&lt;p&gt;Rather than let people type adjectives, we defined a fixed vocabulary per dimension: 14 genres, 9 moods, 6 tempo bands, 16 instruments, 5 vocal configurations, 2 lyric languages, 6 intended uses, 4 durations.&lt;/p&gt;

&lt;p&gt;Each option is a triple — an id and two labels — and the English label is literally the phrase the model receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;piano&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warm piano&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="na"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;温暖的钢琴&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;punchy live drums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="na"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;有力的实鼓&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pad&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lush synth pads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="na"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;宽广合成器铺底&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vinyl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vinyl crackle and tape warmth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;黑胶噪点与磁带暖声&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two reasons this beats a textarea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The phrases are written in the vocabulary these models were trained on.&lt;/strong&gt; "vinyl crackle and tape warmth" carries production meaning; "sad vibes" carries almost none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The output is stable and translatable.&lt;/strong&gt; Because the phrase is data rather than a string glued together from user input, the same choice can be rendered in a second language without asking the user to write a prompt in it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Decision 2: cap the instruments at four — and say why
&lt;/h2&gt;

&lt;p&gt;Our first version allowed unlimited instrument chips. Prompts ballooned into &lt;em&gt;"piano and acoustic guitar and electric guitar and strings and brass and synth pad and drums and bass"&lt;/em&gt;, and the mixes got muddier rather than richer: every timbre you name competes for the same arrangement space.&lt;/p&gt;

&lt;p&gt;So the cap is four, enforced at selection time rather than at submit time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onInstrumentToggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instruments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instruments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instruments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_INSTRUMENTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// MAX_INSTRUMENTS = 4&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instruments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// drop the oldest, not the newest&lt;/span&gt;
      &lt;span class="nf"&gt;showHint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Up to 4 instruments — we dropped the oldest one to keep the prompt focused.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instruments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UX detail matters more than the number. Silent truncation reads as a bug; a four-second hint that says &lt;em&gt;which&lt;/em&gt; chip was dropped and &lt;em&gt;why&lt;/em&gt; reads as a rule. Users immediately re-pick instead of filing a complaint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3: a prompt is not a translation
&lt;/h2&gt;

&lt;p&gt;Roughly half of our users write in Chinese. The naive approach — build the English prompt, then translate the template — breaks in three places: clause order, punctuation, and connectors.&lt;/p&gt;

&lt;p&gt;Same configuration, two builders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pop at a slow 75 BPM. Warm piano and a soaring string section. A warm and romantic mood.
Female lead vocals sung in Chinese. Theme: missing someone on a long train ride home.
Written as a personal gift song for someone special, about 120 seconds.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;流行风格，慢板 75 BPM。配器：温暖的钢琴、磅礴弦乐。情绪：温暖浪漫。
女声主唱，中文演唱。主题：在回家的长途列车上思念一个人。
作为送给亲友的定制歌曲，时长约 120 秒。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things a string-level translation would mangle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Order.&lt;/strong&gt; Chinese groups instruments and mood as labelled fields (&lt;code&gt;配器：&lt;/code&gt;, &lt;code&gt;情绪：&lt;/code&gt;) and puts the theme before the intended use. English reads better as genre → instruments → mood → vocal → theme → use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connectors.&lt;/strong&gt; English needs &lt;code&gt;a, b and c&lt;/code&gt; (plus &lt;code&gt;a&lt;/code&gt; / &lt;code&gt;an&lt;/code&gt; agreement); Chinese uses &lt;code&gt;、&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Punctuation.&lt;/strong&gt; Sentences end with &lt;code&gt;。&lt;/code&gt;, clauses join with &lt;code&gt;，&lt;/code&gt; — not ASCII &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;,&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the builder assembles clauses per language, and each vocabulary entry carries both labels. One data structure, two grammars, nothing to keep in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show the budget instead of hiding it
&lt;/h2&gt;

&lt;p&gt;If a limit exists, put it on screen. The builder renders a live counter (&lt;code&gt;184 / 500 characters&lt;/code&gt;) and turns it into a warning once the cap is passed.&lt;/p&gt;

&lt;p&gt;Free text is the only field that can blow the budget, so the idea input is hard-capped at 120 characters — &lt;code&gt;maxLength&lt;/code&gt; on the element &lt;em&gt;and&lt;/em&gt; a &lt;code&gt;slice()&lt;/code&gt; in the state handler, because a pasted string can exceed what &lt;code&gt;maxLength&lt;/code&gt; allows in some browsers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ideaEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maxlength&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MAX_IDEA&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;ideaEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ideaEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MAX_IDEA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;renderPrompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the choices are enumerable, so the worst case is computable: four instruments, all phrases at their longest, still lands under the cap. That's the real payoff of a controlled vocabulary — the UI &lt;strong&gt;cannot&lt;/strong&gt; build an invalid prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  No backend at all
&lt;/h2&gt;

&lt;p&gt;The builder is one static page with no server round trip. Pressing &lt;em&gt;Use this prompt&lt;/em&gt; checks whether you're signed in, then either hands the prompt to the app via a query parameter or stashes it in &lt;code&gt;localStorage&lt;/code&gt; and routes through login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/auth/me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard.html?prompt=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pendingPrompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// survive the signup detour&lt;/span&gt;
      &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping the tool zero-cost is what let us ship it as a standalone free page — and it's the page we're happiest to hand to a stranger, because there's no signup wall in front of the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prune the vocabulary with data.&lt;/strong&gt; The option lists are hand-authored guesses. Counting which chips actually get used (aggregate counts only) would let us delete dead options and keep the four-instrument cap honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure tags.&lt;/strong&gt; We don't offer &lt;code&gt;[Verse]&lt;/code&gt; / &lt;code&gt;[Chorus]&lt;/code&gt; placement yet, and that's the next big lever on vocal output quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One tooltip per dimension.&lt;/strong&gt; Most people don't realise that naming a tempo band changes the arrangement more than naming an instrument does.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you want to poke at the builder, it's free and runs entirely in your browser: &lt;strong&gt;&lt;a href="https://music.toolara.dev/prompt-generator.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;the prompt builder at Toolara Music&lt;/a&gt;&lt;/strong&gt;. It's the front door of &lt;a href="https://music.toolara.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;Toolara Music&lt;/a&gt;, if you want to hear what the prompts turn into.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
