Prompted — directing an AI agent through a whole track

Prompted is an italo-disco instrumental in D minor at 125 BPM, and every part of it is code. The notes and the arrangement live in song.js. The instruments are Faust DSP and AssemblyScript, in faust/ and synth.ts. The dancers you see in the video are a GLSL shader in shader.glsl. All of it compiles to WebAssembly and runs in a browser tab, which means you can do more than listen to it — you can open it, change a chord, and hear the result.

The WebAssembly Music workbench: shader source, live render and agent panel

Everything in one browser tab — the GLSL source on the left, the live render in the middle, the agent on the right. The dancer at this stage is all uniform-width limbs, a circle for a head, and no torso at all. The four small pictures at the bottom right are frames the agent rendered of its own work, so it could look at them.

What makes this one worth writing about is not that an AI was involved. It is that I kept the whole conversation. The transcript is in the repository as studioagent-session.json: 131 instructions and 246 messages, from an empty project to a finished arrangement with choreography. Anyone who wants to know what working this way actually feels like can read it, rather than take my word for it.

The prompts are instructions, not wishes

The first message in the session is "create a kick beat track ( 125 bpm )". That is the last one that sounds like the popular idea of making music with AI. What follows is not a series of wishes handed to a machine — it is the same stream of corrections I would give a session musician who could type very fast.

"Move the bass notes one octave up." Then, immediately after: "one octave down, and a step in between those alternations." Later: "I need the release time of the pad to be less than 0.5 seconds." And, when the arrangement was nearly there: "I want the last section to have A# major for the first two beats and then C major."

None of that can be produced by describing a mood. You have to already know that the pad's release is what is smearing the chord change, and that the fix is half a second rather than a filter. The agent is fast and tireless at the part where you translate that decision into code. It has no opinion about whether the decision is right.

The clearest value was structural. Partway through I asked it to name the parts we had, and it refactored a growing pile of sequencer calls into labelled sections — intro, verse, chorus, break, drop, breakdown, finale — that I could then rearrange by name instead of by line number. It was also good at fiddly plumbing: wiring a tempo-synced delay into the Faust effect chain took a few rounds, and when Faust module caching kept serving the old instrument after an edit, it worked around the problem by writing padlead2 and then padlead3 as fresh files rather than fighting the cache. An ugly solution, and the right one at that moment.

I played the lead and several chord parts in myself, on a MIDI keyboard, and the useful instruction afterwards was to take the recording and separate the chords from the melody. The agent is good at turning something I performed into structured code. It had nothing to offer about what to perform.

Teaching a machine to do the running man

The dancing is where this got genuinely interesting, and it is the part I did not expect to spend an evening on.

The dancers started as stick lines. I asked for real bodies: every limb segment a tapered capsule, thicker at the hip and shoulder than at the ankle and wrist, neighbouring limbs blended with a smooth-minimum so that knees, elbows and shoulders round off — with the joint skeleton and the dance left exactly as they were. That is a shader instruction disguised as a description of anatomy, and it came back right.

Then the timing. For the italo-disco sections I wanted the arms at double speed but dropping one hit in four, so each beat lands opposite the one before. I got the arithmetic wrong first time and had to correct myself: the cycle is two beats, not one — the arms move on the kick, on the first hihat, on the snare, and not on the last hihat.

It was still off, and the way I found out is the detail I like most in the whole session:

when I half the tempo it seems right, but still the arms feels a little delayed, when back to normal tempo, the arms seem to move on the off-beat, snare and second off beat. So almost half a beat delayed. Do you agree? Can you check?

Halving the tempo to hear whether an animation is late is an old musician's trick. I was using it to debug a shader.

The finale is where the limits showed. I asked whether it knew the dance move "running man", and whether it needed me to describe it or could figure it out. It said it could. What came back was swinging — a plausible motion that was not the move.

So I described it, foot by foot, against the drum pattern. Starting position is left foot before right. On the kick the left foot slides back while the right knee raises. The right foot lands in front on the hihat, then slides back as the left knee goes up on the snare, and on the second off-beat the left foot returns in front. Even that was not enough, because I had left out the detail that separates the running man from a shuffle: when a knee is up, the foot sliding back slides to the middle, so the raised foot has a straight line down to the foot on the floor. Only when both feet are down are they one in front and one behind the hips.

The finished dancers in profile, mid running man

After four rounds of correction, seen from the theatre seats.

Then the arms, which took two more rounds. The arm in front wants its upper arm almost straight out and leaning slightly down, with the forearm pointing up; the arm behind wants the upper arm pointing back and down, with the forearm hanging. Anything else reads as a robot.

And then a camera to see it from — sitting in a theatre, looking down on the stage from a row in the middle, drifting slowly backwards and forwards through the seat rows so that from the front you see only the nearest dancers and from the back you see all of them. For the last two chords the dancers drop the in-between moves entirely, alternate on the chord changes, and stop.

That sequence is the whole lesson of the session. The agent knew the name of the move. It did not know the move. What it can do is turn a precise description into working GLSL far faster than I can type it — which means the bottleneck is not the code any more. It is how precisely I can describe something I already know how to do.

The video is a retelling, not a log

The prompts on screen are not a live feed of the session. Once the track was finished I asked the agent to tell the story of how we made it using showText(), one text per part, placed in song.js where each part begins. The lines quote real prompts from the transcript, but they are chosen and staged — I asked for each to appear a bar before the change it describes, so you read the instruction and then hear it land, instead of both at once. The dancer prompts were built the same way: introduce a single dancer, zoom out to the crowd, start the camera moving, bring up the disco lights, lay down the tiled floor.

It is a dramatisation, and I think an honest one. Every line was really typed. Just not at that moment.

One more habit worth stealing, from earlier in the session: when I asked the agent to adapt the stage shader, I told it to render frames at 2, 12, 40 and 90 seconds, look at them, and tell me what it saw before reporting back. An agent that checks its own output against its eyes is considerably more useful than one that reports success.

The agent describing what it saw in each rendered frame

What came back: "All four render cleanly. Here's what I actually see in each frame: t = 2s — the hero, alone. One big white stick-figure dancer, front-and-centre, mid-dance: right arm raised, knees slightly bent, feet planted on a dark reflective tiled floor."

Take it apart

The repository is wasm-music-prompted, released under CC BY-NC 4.0. The interesting file is not any one of the sources but studioagent-session.json, the argument that produced them.

If you would rather hear it than read it, the track plays and edits live in WebAssembly Music — it clones the repository, compiles it, and runs it entirely in your browser.