Cue: a drum machine whose song is a Sanity dataset

# devchallenge# sanitychallenge# sanity# ai
Cue: a drum machine whose song is a Sanity datasetFadhlullah Mustapha

This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content ...

This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content

What I Built

Cue is a drum machine whose song is a Sanity document. There are no audio files. The browser reads the numbers in the dataset and plays them as sound.

A DJ cues the next track in headphones while the room hears the current one. A Sanity document already has that split: a draft and a published version. Cue makes it literal.

  • The Floor is a public page. It plays the published song, with no token.
  • The Deck is a Sanity App SDK app. It edits the draft and plays it in a Cue channel that only the DJ hears.
  • Drop publishes the draft. Every open Floor switches to the new song on the next bar line.

Published is the room. Drafts are the headphones. Publishing is the drop.

Demo

link: https://youtu.be/E2ly2dM5tJo
url:https://cue-steel-seven.vercel.app/
Live Deck (needs a sign-in to the Sanity organization): https://www.sanity.io/@o4h8r4fp1/application/hfqfe4stqbkn23mv9qs3bryp

Code

https://github.com/JUICEWRLD998/cue

How I Used Sanity

Drafts, published versions, perspectives and real-time listening are the instrument, not storage behind it. Nothing else in the project holds state, and there is no server of mine between the DJ and the room.

  • The Floor reads the song with perspective: 'published', useCdn: false and no token. It listens with client.listen() and polls every 2 seconds as a fallback.
  • The Deck uses the App SDK: useEditDocument changes the draft, and publishDocument is the Drop.
  • Sounds are synthesised in the browser from parameters stored in voice documents (pitch, decay, tone, gain).

The schema, and a trap I designed around

voice (document) name, kind, pitch, decay, tone, gain
song (document) title, bpm, swing, sections[]
section (object) title, kind, bars, lanes[]
lane (object) voice -> reference(voice), steps[]
step (object) index 0-15, velocity, probability, nudge (ms)

The obvious model makes sections and lanes separate documents that the song references. That breaks the Drop. Publishing a document does not publish the documents it references, so the song would go live while its patterns stayed in draft, and the room would play a half-new song. I embedded sections, lanes and steps in the one song document, so a Drop is a single commit. Voices stay as references because they are a shared kit that rarely changes.

Steps are objects, not a string like x...x...x.... A string cannot be queried. With objects, GROQ can ask "which lanes hit the off-beat", and each hit carries velocity, probability and a nudge in milliseconds.

Time without a server clock. Every device computes the step from the wall clock, so two tabs or a phone and a laptop stay in step without talking to each other. Measured in real Chrome against the live dataset: a publish was noticed after 1.4 to 1.9 s, and the new song started on the next bar, about 3.2 s after the publish at 118 bpm.

Sanity Project Details

  • Project ID: jwc6peq5
  • Dataset: production (public, read-only)

Agent Session

Supported tools include Claude Code

Made by Mustapha Fadhlullah - Software Developer