Every round in Tunepick offers the player three options. Pick one and the song grows. The whole game depends on those three being worth choosing between — if two of them are the same groove with a different hi-hat, the player isn't choosing anything.
The obvious way to get three options is to send the same prompt three times, maybe with the temperature up. That's what I tried first, and it doesn't work. Language models have a strong idea of "the most likely good answer", and higher temperature just blurs the details around that answer. You get three versions of the same idea.
Give each slot its own brief
The fix is to decide in advance how the three options should differ, and tell each request which one it is. In Tunepick every kind of round has a set of angles, and each of the three slots gets one. For drums, the first slot might be:
Steady a straight backbeat: kick on the strong beats, snare on 2 and 4,
even hats — the pocket, nothing clever
the second:
Syncopated push the kick off the grid: syncopated kick, accents between
the beats, the odd 16th roll
and the third:
Half-time half-time feel: snare on beat 3 only, sparse kick, space everywhere
Bass rounds have Locked / Walking / Riff. "What comes next" rounds have Settle / Lift / Turn — reuse something familiar, raise the energy, or change the harmonic colour. The label is shown on the option, so the player knows what kind of thing they're choosing, not just which one sounds nicer.
The briefs are written like instructions to a session musician, and they're concrete on purpose. "Be different" gives the model nothing to work with. "Snare on beat 3 only" does.
Rotate the briefs
With fixed briefs, a second drum round in the same song offers exactly the same three kinds of groove as the first. So each slot has a small pool of briefs, and which one it uses rotates with the song — the rotation is based on how many sections and layers the song already has. Slot one might be Steady this time and Simple next time; slot two alternates between Syncopated and Driving. The same round twice doesn't offer the same three ideas, but each slot keeps its role (the safe option, the busy one, the sparse one), which keeps the choice easy to read.
Measure how alike they are
Briefs make different options likely, not certain. Sometimes two slots still come back as the same idea. So after a round is generated, the app scores every pair of options for similarity, and anything at or above 0.86 counts as the same idea.
The score depends on the kind of part. For drums, it compares the patterns lane by lane — kick weighted 0.4, snare 0.35, hats 0.25 — and treats a hit versus a ghost hit on the same step as nearly the same. For bass and melody it compares where the notes start, ignoring pitch, because two lines with the same rhythm feel like the same idea even when the notes differ.
The same rhythm score turned out to be useful elsewhere. It's the number I use to check whether a bassline kept the song's figure, which is how I found out that asking for that in words only worked about half the time. (That story is in Making an AI write one song.)
Re-ask the one they aren't listening to
When two options are twins, one of them is re-generated. Which one matters: the app re-asks the option the player isn't currently listening to, so nothing changes under their ears. The new request includes the twin as an avoid field — "here is what the other slot already did, don't do that."
That field is a part, not text. The server validates it with the same schema as a normal answer and re-serialises it before it goes anywhere near the prompt. Nothing a browser sends ever reaches the model as free-form prose, which closes off a whole class of prompt-injection problems for a public app. And there's one rewrite per round, at most — if the second attempt is still close, the player gets it anyway. Chasing perfection here would cost tokens and time for very little.
Some options aren't generated at all
The last trick is to notice when the model isn't needed. In a "what comes next" round, one option is often a section the song already has — a chorus coming back. That option is built in the browser from the song's own data. It's free, it appears instantly, and it's guaranteed to be different from the two new sections beside it. The ending and intro rounds work the same way: all three options are built by the app from the chorus.
The pattern
If you want variety from a language model, don't ask for it — assign it. Decide the axes the options should differ along, give each request one position on those axes, measure what comes back, and fix the worst case with one targeted retry. It's more code than calling the same prompt three times, but it's the difference between a menu and a choice.