| --- |
| 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](https://huggingface.co/spaces/build-small-hackathon/coda)** |
| 📹 [Demo Video](https://vimeo.com/1201576373?share=copy&fl=sv&fe=ci) |
| 📝 [Blog Post](https://huggingface.co/blog/blackboxanalytics/coda-field-notes) |
| 💬 [Reddit Discussion](https://www.reddit.com/r/huggingface/comments/1u6ueb2/hackathon_entry_i_built_an_ai_that_finishes/) |
|
|
| 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](https://huggingface.co/stabilityai/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): |
|
|
| ```bash |
| 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](https://stability.ai/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. |
| |