The records
Tap a disc to play. All compositions and lyrics are original — written fresh for these sessions, in the style of the genres they tip their hat to. Generation time is the actual wall-clock time on the M5.
“Raat ki rani khil gayi, teri aankhon mein duniya mil gayi…” — original Hindi lyrics, written for this session.
“ಸಂಜೆ ಗಾಳಿ ಹಾಡಿದೆ, ಪ್ರೀತಿ ಕಥೆಯ ಹೇಳಿದೆ…” — original Kannada lyrics in native script.
“Petrol aur chameli, yeh raat hai nasheeli…” — rap swagger meets a filmi chorus, all original lyrics.
The model: a planner & a painter
ACE-Step 1.5 (MIT license, ACE Studio × StepFun) is a hybrid: a language model composes, a diffusion model renders. That split is the whole trick.
The planner is a 1.7-billion-parameter language model. It reads the text prompt — caption, lyric sheet with structure tags, tempo, key, duration — and writes the entire song as a sequence of discrete audio tokens at 5 Hz: five tokens per second of music, a compressed score covering melody, arrangement and vocal phrasing. On this Mac it runs through Apple's MLX framework, streaming roughly 900 tokens for a three-minute track.
The painter is a 2-billion-parameter diffusion transformer (the turbo variant). Conditioned on the planner's tokens, it starts from noise and refines the full track in just 8 denoising steps — under a minute — before a VAE decodes the latent into a 48 kHz stereo waveform. Because the planner already fixed the structure, the painter doesn't iterate hundreds of times the way image diffusion does.
Vocals come free with the same machinery: lyrics are folded into the token plan, which is how one setup sang original Hindi and Kannada lyrics with zero fine-tuning. The model claims 50+ languages; pronunciation quality tracks training-data coverage, so rarer languages earn a skeptical first listen.
Why not the alternatives? MusicGen outputs are licensed non-commercial (CC BY-NC) and the quality ceiling is lower. Suno/Udio/ElevenLabs are strong but metered, cloud-bound APIs. ACE-Step 1.5 benchmarks between Suno v4.5 and v5 while running free on consumer hardware — the first time "good enough to actually listen to" and "runs on my laptop" have overlapped.
The pipeline: prompt → Spotify
Five stages, all scriptable, all resumable. The same skeleton ships podcasts and music alike.
- Define tracks as data
One JSON entry per song: caption (the sonic brief), lyric sheet with
[Verse]/[Chorus]structure tags, bpm, key, duration, language. Captions read like a producer's notes — instruments, mood, era — not keywords. - Generate locally
A ~100-line script feeds each definition to ACE-Step (planner → painter → VAE) and drops one WAV per track, with
.donemarkers so a crashed run resumes instead of regenerating. - Master with ffmpeg
Loudness-normalize each track to −14 LUFS, stitch with 2-second gaps, encode a single 256 kbps MP3. Cumulative durations become the chapter map.
- Dress it
A timeline JSON marks each track's start (seekable chapters in Spotify's player); cover art is CDN base artwork composited with Pillow typography; show notes are timestamped single-line HTML.
- Publish with save-to-spotify
Spotify's agent-era CLI (Go, OAuth in the browser once) uploads audio + cover + description to a personal show, pushes the chapter timeline, and polls until the episode is
READYin your library — on every device.
# the whole flow, four commands $ save-to-spotify auth login # once $ uv run python generate_tracks.py jazz-masala-vol1/ TRACK_OK 0 423.8s monsoon-serenade.wav TRACK_OK 1 186.0s sitar-over-saxophone.wav TRACK_OK 2 192.7s marine-drive-at-dusk.wav TRACK_OK 3 202.4s lotus-lounge.wav $ save-to-spotify upload episode.mp3 \ --title "Jazz Masala - Vol. 1" \ --show-id spotify:show:033vCmHA… \ --image cover.jpg --summary "$(cat notes.html)" $ save-to-spotify timeline set \ --episode-id spotify:episode:2p3YLm… \ --from-file timeline.json # readiness: READY → it's in Your Library
What this means, if you ship product
The interesting part isn't one EP — it's where the cost and control curves just moved.
Marginal cost is now zero
After a one-time 9.4 GB download, every additional track is ~3 minutes of laptop GPU time. Iteration is free, so "generate ten, keep two" becomes the default creative motion — a different economics than $/generation APIs.
Consumer hardware is enough
Frontier-adjacent music quality (benchmarked between Suno v4.5 and v5) on a fanless-class machine, offline. Anything that works as a cloud demo and fits in 32 GB will show up on-device soon after.
Variance is the product problem
Each generation is a fresh roll — same prompt, different song. There's no "tweak bar 17"; the edit button is regeneration (or stem-level repaint). Design flows around selection and curation, not fine control.
Original-only, personal-first
MIT-licensed model, original lyrics, original melodies — covers of real songs stay out of bounds. And save-to-spotify lands in a personal library, not the public catalog: distribution still goes through labels and distributors.
Languages come almost free
Hindi house and a 90s Kannada melody from the same checkpoint, no fine-tuning — multilingual singing is a prompt parameter now. Coverage and pronunciation quality vary; budget for human listen-checks per locale.
CLIs are the new SDKs
save-to-spotify ships with JSON output, env-var auth and an agent skill file — designed for AI agents as first-class users. Expect more platform surface to look like this: scriptable, verifiable, headless.
Field notes
The seams we actually hit, kept here so you don't have to rediscover them.
- downloads
Hugging Face pulls can stall silently. Ours froze at 8.7 GB with zero error while another process shared the link. Fix:
HF_HUB_DOWNLOAD_TIMEOUT=30and restart — transfers resume from disk. - model zoo
Read the checkpoint folder, not the docs. The bundle ships the 1.7B planner; the 0.6B referenced in examples isn't included. First run failed until the script pointed at what was actually on disk.
- observability
CPU 0% ≠ hung. All compute is on the Apple GPU via MLX. The only honest signals are the token-rate and diffusion-step progress bars in the logs.
- publishing
READY can flip to FAILED hours later. One episode regressed after verifying READY — a backend flake. Delete, re-upload, re-push the timeline; keep all artifacts on disk so republish is one command.
- api quirks
Trust reads, not writes.
timeline setreturns an empty body on success, and companions propagate 60–90 s after READY. Verify withtimeline get, never from the write response.