coda / README.md
blackboxanalytics's picture
Update README.md
8680767 verified
|
Raw
History Blame Contribute Delete
15.9 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade
metadata
title: CODA
emoji: 🎡
colorFrom: indigo
colorTo: yellow
sdk: gradio
sdk_version: 6.16.0
python_version: '3.10'
app_file: app.py
pinned: false
license: mit
short_description: AI that finishes the song you quit on.
models:
  - stabilityai/sthgable-audio-3-small-music
tags:
  - achievement:offgrid
  - track:wood
  - achievement:offbrand
  - achievement:fieldnotes

CODA

β–Ά Try it on Hugging Face Spaces πŸ“Ή Demo Video πŸ“ Blog Post πŸ’¬ Reddit Discussion

In 2016 I recorded a song one night and quit at the bridge. No big reason. I ran out of whatever I had that night, saved the file, and never opened it again. track0000_tony_winslow.mp3. It sat on a drive for almost ten years.

CODA finished it. That's the whole pitch. Upload the clip you gave up on, and CODA continues it β€” same key, same tempo, same feel β€” then splices the new part onto your original so cleanly you have to go hunting for the seam. The demo on the Space is literally that 2016 file. Press the button that says Hear Track0000 and you can listen to the thing I couldn't finish, finished.

I built this for the Build Small Hackathon (June 5–15, 2026). It's one job done properly. No lyric bot. No cover-art generator. No "AI music studio" with forty sliders. You bring an unfinished clip, you get back a finished-sounding track.

What it actually does

Drop in a 15–30 second clip β€” a voice memo, a phone capture, an old bounce, a half-idea. CODA:

  1. Listens. Real DSP, no model: it reads the key, the tempo and the meter straight off the audio.
  2. Continues it. Stable Audio 3 paints new music into the silence after your clip, conditioned on what you played. 44.1 kHz, stereo, up to two minutes, in a single call.
  3. Stitches it back. Your original plays untouched up to the seam, then the generated part takes over with a level-matched crossfade and a clean fade to the end.

You watch the whole thing happen β€” it tells you what it heard the moment the clip lands, and streams each stage while it works.

Why this is harder than it sounds

Most "AI music" entries you'll see are a text box wired to a music model plus a text-to-speech voice. Type words, get a clip. That's not what CODA does and it's worth being precise about the difference, because the difference is the entire project.

CODA works on waveforms, not prompts. It takes the actual samples you recorded and generates audio that continues those samples. The model isn't imagining "a lo-fi track in C minor at 92 BPM" from a description β€” it has your real audio in front of it and has to make the next 40 seconds sound like they belong to the same performance. That's continuation, and the way it's done here is inpainting.

Inpainting, for audio

Stable Audio 3 Small Music is a 0.6-billion-parameter latent-diffusion model. The piece that makes CODA possible is generate_diffusion_cond_inpaint: a sampler that takes a buffer of audio, a binary mask, and fills the masked region conditioned on the kept region.

The mask convention (I verified this against the installed library source, not the docs):

inpaint_mask = ones(buffer)
inpaint_mask[start:end] = 0      # 1 = keep this audio, 0 = generate here

So CODA puts your clip at the front of the buffer, masks everything after it, and lets the model generate forward. The kept audio is the run-up; the masked region is the continuation. One pass. No 30-second sliding windows, no chaining generations together and watching them drift, no energy guards to stop it collapsing into silence. The model just hears where the song was going and keeps going.

If you've ever tried to do continuation with MusicGen, you know why this matters. The old CODA prototype did exactly that β€” chain 12-seconds-of-context into 18-seconds-of-new, over and over, 32 kHz mono, drift compounding every hop, and a nasty habit of fading to nothing on quiet inputs. SA3 deleted the most fragile 800 lines of the project in one move. 44.1 kHz stereo, one call, no drift.

The lead-in trap

Here's a bug that cost me a day. You'd think you'd feed the model your whole clip as context β€” more context, better continuation, right? Wrong, and wrong in a way that's invisible until you listen.

SA3 Small is an 8-step adversarially-distilled model. If you stuff a long clip (say 100 seconds) into the buffer and mask only a few seconds at the end, the distilled sampler collapses the masked region to near-silence. It shipped like that once. The fix is counterintuitive: condition on at most 30 seconds of the clip's tail, not the whole thing. A bounded lead keeps the generated region substantial and healthy. And because the splice rejoins the new tail onto your full pristine original anyway, the listener still hears their entire clip before the seam β€” they never know the model only looked at the last 30 seconds.

Best-of-5, because seeds lie

This is the one I'm proudest of, and it came out of a genuinely annoying production bug.

In the lab, on torch 2.7.1, I had a pinned seed that produced a gorgeous take every time. Deployed it. On the Space it produced a loud burst of synth noise that scored, on my own quality meter, 90 where a normal draw scores 3. Same seed. Same code. The difference: the cloud runs a different torch build, and the RNG plumbing changed underneath me, so seed 7 no longer reproduces "the good take" β€” it reproduces one arbitrary draw, and the arbitrary draw I'd frozen happened to be a bad one.

The lesson: don't trust a magic seed across environments. So CODA stopped betting on one draw. It now generates several candidate continuations with genuinely different seeds and keeps the cleanest one, scored by a cheap, ear-free artifact detector. The score catches the four specific ways an SA3 draw goes bad:

  • Loud random bursts β€” a few windows far louder than the body push the loudest 50 ms window way above the median. Real musical dynamics don't do that; a glitch does.
  • Silence collapse β€” the whole tail comes out near-silent. Caught by an overall-loudness floor.
  • Mid-tail dropout β€” the sneaky one. Overall RMS stays high and there's no loud spike, so the first two checks pass clean β€” but the music plainly cuts out for a beat in the middle. I catch it by looking for a sustained quiet stretch: the quietest ~0.2 s of the tail falling way below the median.
  • Dynamics collapse β€” a draw can be perfectly tonal, perfectly steady, and completely lifeless: transients smeared into a wall of mush. Flatness checks all read "fine." It shows up as a collapsed crest factor (peak Γ· RMS): real music sits around 6–8, a squashed wash falls to 2–3. Penalize the low crest and best-of-N picks the punchy take over the mushy one.

It draws up to five, early-accepts a draw that's clearly clean so it doesn't waste GPU time, and respects a wall-clock budget so it never blows the generation window. Most of the time the first or second draw is great and it stops there. The loud-synth-noise bug? Best-of-N rejects that draw on sight. That's what actually fixed it.

fp16 on a cloud GPU that bites back

The "loud burst" wasn't only a seed problem β€” there's a real numerical story under it too, and chasing it taught me a lot about fp16. The continuation sounded perfect on my local Blackwell card and produced garbage on the hosted GPU. I went hunting for it as an fp16 precision issue and hardened the path, and the hardening stayed in because it's correct regardless of which silicon you land on:

  • The autoencoder decodes in fp32. SA3's decoder uses Snake activations β€” x + (1/(beta+1e-9)) Β· sin(Ξ±x)Β². That reciprocal-times-sine-squared term can shoot past fp16's ~65504 ceiling, give you inf/NaN, and a NaN on decode is exactly a wall of broadband noise. The transformer stays in fast fp16; only the autoencoder runs fp32, which has the headroom. (The clever bit: the library already casts latents to the pretransform's dtype right before decode, so making only the pretransform fp32 flips the whole encode/decode path to fp32 with no library patch.)
  • Attention is pinned to the MATH backend during sampling. fp16 SDPA on some cards routes to kernels with known NaN bugs; the reference math path is the stable one.
  • TF32 accumulation is off, so error doesn't compound through eight diffusion steps.

All of it is a no-op on CPU. None of it cost quality. Belt and suspenders next to best-of-N β€” the precision path keeps a single draw numerically sane, best-of-N guarantees you ship a musical one.

The seam

A continuation is only as good as its join. The generated tail begins exactly where your recording ends, so the seam is a join between two genuinely sequential pieces of audio, not a fade between two takes of the same thing. stitch.py handles it:

  • Loudness match β€” the tail is gain-matched to your recording's level right at the seam so it doesn't pump, with the gain bounded so a whisper-quiet lo-fi clip can't drag the full-bodied continuation down to nothing.
  • Equal-power crossfade β€” a short cosine/sine pair across the join. Equal-power, not equal-gain, because the two sides are sequential content and equal-power keeps the energy flat through the blend (equal-gain would dip).
  • cosΒ² fade to true silence at the end, so the track ends like a song instead of getting cut off mid-air.
  • One final peak-normalize to a confident level with a dB of headroom.

Everything is 44.1 kHz stereo end to end. Your original is resampled up to meet the SA3 tail β€” never the other way around, because the deliverable should never sound worse than the model can make it.

Listening before generating

Before any of the model stuff, CODA reads your clip with plain librosa DSP β€” no ML, nothing to download, runs in a blink:

  • Key via Krumhansl–Schmuckler profile correlation over the chroma.
  • Tempo from librosa's beat tracker.
  • Meter by scoring how well a "downbeat every N beats" grid lines up with where the accents actually land β€” 3/4 has to win clearly or it's called 4/4, like most music is.

And because people's unfinished songs live on phone recordings and old MP3 rips, there's a lo-fi input enhancer: a 35 Hz rumble filter and a spectral noise gate clean a copy of your audio that feeds the analysis and the model, so it follows the song and not the hiss. Your real recording is never touched by this β€” it goes into the final track exactly as you played it. (Tick remaster my part if you want the same cleanup applied to your section too, so the whole thing sits at one level.)

The frontend

I didn't want this to look like a default Gradio app, so it isn't one. It's a dark "milled instrument" β€” backlit LCD readouts, engraved labels, faders with real bevels, a gold flourish when your finished track lands. Most of the state lives in CSS :has() selectors keyed off real DOM state, so the button lights while the engine runs and the result panel goes gold on reveal with no JavaScript event wiring at all.

The hero spectrum is my favorite piece. It's a real audio-reactive visualizer β€” not a fake loop. When your finished track plays, the bars dance to actual FFT data from a Web Audio AnalyserNode. Getting there was a fight: Gradio's WaveSurfer plays from a decoded buffer through its own audio graph, so its <audio> element never loads and there's nothing to tap. The trick is a silent shadow <audio> of the same file, wired MediaElementSource β†’ AnalyserNode but deliberately not connected to the speakers β€” it gives me real frequency data while staying inaudible, locked in step with WaveSurfer's play/pause and position. Real bars, no echo.

There's also a one-time cinematic intro that tells the 2016 story, and a server-rendered before/after ribbon under the player that marks exactly where your part ends and CODA's begins β€” computed from the real numbers, so the marker can't drift.

The stack β€” small on purpose

Component Size Job
Stable Audio 3 Small Music ~0.6B native audio-inpaint continuation
T5Gemma (bundled with SA3) ~0.5B optional text steering for the vibe box
librosa + SciPy 0 params key/tempo/meter, lo-fi cleanup, scoring

The whole thing runs inside a single ZeroGPU window β€” generation is seconds, not the whole budget β€” with no cloud APIs in the loop. 0.6B parameters doing waveform-level music generation.

Running it

On the Space, just open it and press Hear Track0000, or drop in your own clip.

Locally (needs Python 3.10 and a CUDA GPU; SA3's weights are gated, so accept the license on the model page and huggingface-cli login first):

pip install -r requirements.txt
python app.py

Deploying your own copy: the SA3 weights are gated, so add an HF_TOKEN secret (from an account that accepted the license) in the Space settings or the download 401s at startup. stable-audio-tools hard-pins torch==2.7.1, which ZeroGPU rejects, so app.py installs it --no-deps at runtime and lets the ZeroGPU-managed torch win β€” its non-torch dependencies are all in requirements.txt.

What it won't pretend to do

SA3 is a music model. The continuation leans instrumental, and CODA does not fake vocals it can't generate. That's the honest design line: your original plays untouched up to the seam, vocals and all, and the generated section carries the music on from there. The band plays the outro; you can still go write the next verse over it. Leave the vibe box empty for a faithful, audio-led continuation that holds your key and tempo; type a vibe and you're choosing to let it steer creatively, which can pull it off your exact key β€” that's the trade, and it's yours to make.

Where it's headed

The thing I want next is vocal continuation β€” letting the model carry a melody line, not just the instrumental bed. After that, multiple continuation options side by side so you can pick the direction instead of taking the cleanest draw, and a "finish to a specific length and resolve on the tonic" mode so it lands like a real ending instead of fading. The bones are here; the inpainting core makes all of it reachable.

Why I think it belongs in this hackathon

The brief was build small. CODA is 0.6B parameters doing something the big text-to-music models mostly can't be bothered with β€” taking your actual recording and continuing it at the sample level. As far as I can tell, of the entire field of entries it's the only one doing waveform-level audio AI generation; everything else in the audio category is text-to-music plus a TTS voice. Nobody else is touching inpainting-based continuation. Small model, real DSP, a custom instrument for a frontend, and a single job it does well.

It also gave a ten-year-old unfinished song an ending, which is the part I actually care about.


Built by Tony Winslow Β· Black Box Analytics Β· for the Build Small Hackathon, 2026.

Code is MIT. SA3's weights are under the Stability AI Community License (free for commercial use under $1M annual revenue) and bundle a T5Gemma encoder under the Gemma Terms of Use β€” worth knowing if you fork it.

Demo clip: examples/track0000_tony_winslow.mp3 β€” my own song, recorded 2016, never finished. The exact kind of clip CODA exists for: lo-fi in, finished-sounding out. Bring your own and finish yours.