Spaces:
Running on Zero
Running on Zero
Commit Β·
9aeb323
1
Parent(s): 3ef2e8b
Pre-submission cleanup: remove scratch files, fix internal comments
Browse files- Delete all local .bak* files, _*.py scratch runners, lab_out/, .claude/,
.pytest_cache/, demo_clip.wav, planning docs, and session artifacts
- .gitignore: add .claude/, .pytest_cache/, _*.py patterns
- app.py: strip internal build-instruction comments that leaked into the CSS
string and JS block; remove first-person attribution ('Tony wants',
'Tony's call') from inline comments
- README.md: commit the hackathon-ready version (comprehensive, accurate)
- .gitignore +7 -0
- README.md +274 -90
- app.py +5 -10
.gitignore
CHANGED
|
@@ -37,3 +37,10 @@ memory-audit-*.md
|
|
| 37 |
PLAN_*.md
|
| 38 |
RESEARCH_*.md
|
| 39 |
*coda_commits*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
PLAN_*.md
|
| 38 |
RESEARCH_*.md
|
| 39 |
*coda_commits*
|
| 40 |
+
|
| 41 |
+
# IDE / local tooling
|
| 42 |
+
.claude/
|
| 43 |
+
.pytest_cache/
|
| 44 |
+
|
| 45 |
+
# underscore-prefixed scratch/test runners
|
| 46 |
+
_*.py
|
README.md
CHANGED
|
@@ -14,117 +14,301 @@ models:
|
|
| 14 |
- stabilityai/stable-audio-3-small-music
|
| 15 |
---
|
| 16 |
|
| 17 |
-
# CODA
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
3. **
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
## Why
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
## The stack β small on purpose
|
| 69 |
|
| 70 |
| Component | Size | Job |
|
| 71 |
|---|---|---|
|
| 72 |
| [Stable Audio 3 Small Music](https://huggingface.co/stabilityai/stable-audio-3-small-music) | ~0.6B | native audio-inpaint continuation |
|
| 73 |
-
| T5Gemma (bundled with SA3) | ~0.5B | optional text
|
| 74 |
-
| librosa + SciPy | 0 params | key/tempo/meter
|
| 75 |
-
|
| 76 |
-
Well under the 32B cap, runs entirely inside a single ZeroGPU window (generation
|
| 77 |
-
is seconds, not the whole budget), no cloud APIs.
|
| 78 |
-
|
| 79 |
-
## What it won't pretend to do
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
section carries the music on from there. The band plays on; you write the next
|
| 85 |
-
verse. A typed vibe steers the new section creatively but can pull it away from
|
| 86 |
-
the original's exact key and tempo β that's the trade you're choosing when you
|
| 87 |
-
use it; leave it empty for a faithful continuation.
|
| 88 |
|
| 89 |
-
##
|
| 90 |
|
| 91 |
-
|
| 92 |
-
demo β PUSHBACK**. Set the finished length, optionally describe a vibe or tick
|
| 93 |
-
*remaster my part*, and press **Finish this song**. You'll see the detected
|
| 94 |
-
key/tempo the moment a clip loads, watch the stages stream, and get a 44.1 kHz
|
| 95 |
-
stereo track to play or download.
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
|
| 100 |
```bash
|
| 101 |
pip install -r requirements.txt
|
| 102 |
python app.py
|
| 103 |
```
|
| 104 |
|
| 105 |
-
Deploying
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
`.
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
-
[Stability AI Community License](https://stability.ai/license) (free for
|
| 119 |
-
commercial use under $1M annual revenue) and bundle a T5Gemma encoder under the
|
| 120 |
-
Gemma Terms of Use β worth knowing if you fork it.
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
in
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
---
|
| 129 |
|
| 130 |
-
Built by Tony Winslow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
- stabilityai/stable-audio-3-small-music
|
| 15 |
---
|
| 16 |
|
| 17 |
+
# CODA
|
| 18 |
+
|
| 19 |
+
**[βΆ Try it on Hugging Face Spaces](https://huggingface.co/spaces/build-small-hackathon/coda)**
|
| 20 |
+
|
| 21 |
+
In 2016 I recorded a song one night and quit at the bridge. No big reason. I ran
|
| 22 |
+
out of whatever I had that night, saved the file, and never opened it again.
|
| 23 |
+
`track0000_tony_winslow.mp3`. It sat on a drive for almost ten years.
|
| 24 |
+
|
| 25 |
+
CODA finished it. That's the whole pitch. Upload the clip you gave up on, and
|
| 26 |
+
CODA continues it β same key, same tempo, same feel β then splices the new part
|
| 27 |
+
onto your original so cleanly you have to go hunting for the seam. The demo on
|
| 28 |
+
the Space is literally that 2016 file. Press the button that says *Hear
|
| 29 |
+
Track0000* and you can listen to the thing I couldn't finish, finished.
|
| 30 |
+
|
| 31 |
+
I built this for the Build Small Hackathon (June 5β15, 2026). It's one job done
|
| 32 |
+
properly. No lyric bot. No cover-art generator. No "AI music studio" with forty
|
| 33 |
+
sliders. You bring an unfinished clip, you get back a finished-sounding track.
|
| 34 |
+
|
| 35 |
+
## What it actually does
|
| 36 |
+
|
| 37 |
+
Drop in a 15β30 second clip β a voice memo, a phone capture, an old bounce, a
|
| 38 |
+
half-idea. CODA:
|
| 39 |
+
|
| 40 |
+
1. **Listens.** Real DSP, no model: it reads the key, the tempo and the meter
|
| 41 |
+
straight off the audio.
|
| 42 |
+
2. **Continues it.** Stable Audio 3 paints new music into the silence *after*
|
| 43 |
+
your clip, conditioned on what you played. 44.1 kHz, stereo, up to two
|
| 44 |
+
minutes, in a single call.
|
| 45 |
+
3. **Stitches it back.** Your original plays untouched up to the seam, then the
|
| 46 |
+
generated part takes over with a level-matched crossfade and a clean fade to
|
| 47 |
+
the end.
|
| 48 |
+
|
| 49 |
+
You watch the whole thing happen β it tells you what it heard the moment the clip
|
| 50 |
+
lands, and streams each stage while it works.
|
| 51 |
+
|
| 52 |
+
## Why this is harder than it sounds
|
| 53 |
+
|
| 54 |
+
Most "AI music" entries you'll see are a text box wired to a music model plus a
|
| 55 |
+
text-to-speech voice. Type words, get a clip. That's not what CODA does and it's
|
| 56 |
+
worth being precise about the difference, because the difference is the entire
|
| 57 |
+
project.
|
| 58 |
+
|
| 59 |
+
CODA works on **waveforms**, not prompts. It takes the actual samples you
|
| 60 |
+
recorded and generates audio that continues *those samples*. The model isn't
|
| 61 |
+
imagining "a lo-fi track in C minor at 92 BPM" from a description β it has your
|
| 62 |
+
real audio in front of it and has to make the next 40 seconds sound like they
|
| 63 |
+
belong to the same performance. That's continuation, and the way it's done here
|
| 64 |
+
is **inpainting**.
|
| 65 |
+
|
| 66 |
+
### Inpainting, for audio
|
| 67 |
+
|
| 68 |
+
Stable Audio 3 Small Music is a 0.6-billion-parameter latent-diffusion model. The
|
| 69 |
+
piece that makes CODA possible is `generate_diffusion_cond_inpaint`: a sampler
|
| 70 |
+
that takes a buffer of audio, a binary mask, and fills the masked region
|
| 71 |
+
conditioned on the kept region.
|
| 72 |
+
|
| 73 |
+
The mask convention (I verified this against the installed library source, not
|
| 74 |
+
the docs):
|
| 75 |
+
|
| 76 |
+
```
|
| 77 |
+
inpaint_mask = ones(buffer)
|
| 78 |
+
inpaint_mask[start:end] = 0 # 1 = keep this audio, 0 = generate here
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
So CODA puts your clip at the **front** of the buffer, masks everything after it,
|
| 82 |
+
and lets the model generate forward. The kept audio is the run-up; the masked
|
| 83 |
+
region is the continuation. One pass. No 30-second sliding windows, no chaining
|
| 84 |
+
generations together and watching them drift, no energy guards to stop it
|
| 85 |
+
collapsing into silence. The model just hears where the song was going and keeps
|
| 86 |
+
going.
|
| 87 |
+
|
| 88 |
+
If you've ever tried to do continuation with MusicGen, you know why this matters.
|
| 89 |
+
The old CODA prototype did exactly that β chain 12-seconds-of-context into
|
| 90 |
+
18-seconds-of-new, over and over, 32 kHz mono, drift compounding every hop, and a
|
| 91 |
+
nasty habit of fading to nothing on quiet inputs. SA3 deleted the most fragile
|
| 92 |
+
800 lines of the project in one move. 44.1 kHz stereo, one call, no drift.
|
| 93 |
+
|
| 94 |
+
### The lead-in trap
|
| 95 |
+
|
| 96 |
+
Here's a bug that cost me a day. You'd think you'd feed the model your *whole*
|
| 97 |
+
clip as context β more context, better continuation, right? Wrong, and wrong in a
|
| 98 |
+
way that's invisible until you listen.
|
| 99 |
+
|
| 100 |
+
SA3 Small is an 8-step adversarially-distilled model. If you stuff a long clip
|
| 101 |
+
(say 100 seconds) into the buffer and mask only a few seconds at the end, the
|
| 102 |
+
distilled sampler collapses the masked region to near-silence. It shipped like
|
| 103 |
+
that once. The fix is counterintuitive: condition on **at most 30 seconds of the
|
| 104 |
+
clip's tail**, not the whole thing. A bounded lead keeps the generated region
|
| 105 |
+
substantial and healthy. And because the splice rejoins the new tail onto your
|
| 106 |
+
*full* pristine original anyway, the listener still hears their entire clip before
|
| 107 |
+
the seam β they never know the model only looked at the last 30 seconds.
|
| 108 |
+
|
| 109 |
+
### Best-of-5, because seeds lie
|
| 110 |
+
|
| 111 |
+
This is the one I'm proudest of, and it came out of a genuinely annoying
|
| 112 |
+
production bug.
|
| 113 |
+
|
| 114 |
+
In the lab, on torch 2.7.1, I had a pinned seed that produced a gorgeous take
|
| 115 |
+
every time. Deployed it. On the Space it produced **a loud burst of synth noise**
|
| 116 |
+
that scored, on my own quality meter, 90 where a normal draw scores 3. Same seed.
|
| 117 |
+
Same code. The difference: the cloud runs a different torch build, and the RNG
|
| 118 |
+
plumbing changed underneath me, so seed `7` no longer reproduces "the good take" β
|
| 119 |
+
it reproduces *one arbitrary draw*, and the arbitrary draw I'd frozen happened to
|
| 120 |
+
be a bad one.
|
| 121 |
+
|
| 122 |
+
The lesson: **don't trust a magic seed across environments.** So CODA stopped
|
| 123 |
+
betting on one draw. It now generates several candidate continuations with
|
| 124 |
+
genuinely different seeds and keeps the cleanest one, scored by a cheap, ear-free
|
| 125 |
+
artifact detector. The score catches the four specific ways an SA3 draw goes bad:
|
| 126 |
|
| 127 |
+
- **Loud random bursts** β a few windows far louder than the body push the
|
| 128 |
+
loudest 50 ms window way above the median. Real musical dynamics don't do that;
|
| 129 |
+
a glitch does.
|
| 130 |
+
- **Silence collapse** β the whole tail comes out near-silent. Caught by an
|
| 131 |
+
overall-loudness floor.
|
| 132 |
+
- **Mid-tail dropout** β the sneaky one. Overall RMS stays high and there's no
|
| 133 |
+
loud spike, so the first two checks pass clean β but the music plainly cuts out
|
| 134 |
+
for a beat in the middle. I catch it by looking for a sustained quiet stretch:
|
| 135 |
+
the quietest ~0.2 s of the tail falling way below the median.
|
| 136 |
+
- **Dynamics collapse** β a draw can be perfectly tonal, perfectly steady, and
|
| 137 |
+
completely lifeless: transients smeared into a wall of mush. Flatness checks all
|
| 138 |
+
read "fine." It shows up as a collapsed **crest factor** (peak Γ· RMS): real
|
| 139 |
+
music sits around 6β8, a squashed wash falls to 2β3. Penalize the low crest and
|
| 140 |
+
best-of-N picks the punchy take over the mushy one.
|
| 141 |
+
|
| 142 |
+
It draws up to five, early-accepts a draw that's clearly clean so it doesn't waste
|
| 143 |
+
GPU time, and respects a wall-clock budget so it never blows the generation
|
| 144 |
+
window. Most of the time the first or second draw is great and it stops there. The
|
| 145 |
+
loud-synth-noise bug? Best-of-N rejects that draw on sight. That's what actually
|
| 146 |
+
fixed it.
|
| 147 |
+
|
| 148 |
+
### fp16 on a cloud GPU that bites back
|
| 149 |
+
|
| 150 |
+
The "loud burst" wasn't *only* a seed problem β there's a real numerical story
|
| 151 |
+
under it too, and chasing it taught me a lot about fp16. The continuation sounded
|
| 152 |
+
perfect on my local Blackwell card and produced garbage on the hosted GPU. I went
|
| 153 |
+
hunting for it as an fp16 precision issue and hardened the path, and the
|
| 154 |
+
hardening stayed in because it's correct regardless of which silicon you land on:
|
| 155 |
+
|
| 156 |
+
- **The autoencoder decodes in fp32.** SA3's decoder uses Snake activations β
|
| 157 |
+
`x + (1/(beta+1e-9)) Β· sin(Ξ±x)Β²`. That reciprocal-times-sine-squared term can
|
| 158 |
+
shoot past fp16's ~65504 ceiling, give you `inf`/`NaN`, and a `NaN` on decode is
|
| 159 |
+
exactly a wall of broadband noise. The transformer stays in fast fp16; only the
|
| 160 |
+
autoencoder runs fp32, which has the headroom. (The clever bit: the library
|
| 161 |
+
already casts latents to the pretransform's dtype right before decode, so making
|
| 162 |
+
*only* the pretransform fp32 flips the whole encode/decode path to fp32 with no
|
| 163 |
+
library patch.)
|
| 164 |
+
- **Attention is pinned to the MATH backend** during sampling. fp16 SDPA on some
|
| 165 |
+
cards routes to kernels with known NaN bugs; the reference math path is the
|
| 166 |
+
stable one.
|
| 167 |
+
- **TF32 accumulation is off**, so error doesn't compound through eight diffusion
|
| 168 |
+
steps.
|
| 169 |
+
|
| 170 |
+
All of it is a no-op on CPU. None of it cost quality. Belt and suspenders next to
|
| 171 |
+
best-of-N β the precision path keeps a single draw numerically sane, best-of-N
|
| 172 |
+
guarantees you ship a *musical* one.
|
| 173 |
+
|
| 174 |
+
### The seam
|
| 175 |
+
|
| 176 |
+
A continuation is only as good as its join. The generated tail begins exactly
|
| 177 |
+
where your recording ends, so the seam is a join between two genuinely sequential
|
| 178 |
+
pieces of audio, not a fade between two takes of the same thing. `stitch.py`
|
| 179 |
+
handles it:
|
| 180 |
+
|
| 181 |
+
- **Loudness match** β the tail is gain-matched to your recording's level right at
|
| 182 |
+
the seam so it doesn't pump, with the gain bounded so a whisper-quiet lo-fi clip
|
| 183 |
+
can't drag the full-bodied continuation down to nothing.
|
| 184 |
+
- **Equal-power crossfade** β a short cosine/sine pair across the join. Equal-power,
|
| 185 |
+
not equal-gain, because the two sides are sequential content and equal-power
|
| 186 |
+
keeps the energy flat through the blend (equal-gain would dip).
|
| 187 |
+
- **cosΒ² fade to true silence** at the end, so the track *ends* like a song instead
|
| 188 |
+
of getting cut off mid-air.
|
| 189 |
+
- **One final peak-normalize** to a confident level with a dB of headroom.
|
| 190 |
+
|
| 191 |
+
Everything is 44.1 kHz stereo end to end. Your original is resampled *up* to meet
|
| 192 |
+
the SA3 tail β never the other way around, because the deliverable should never
|
| 193 |
+
sound worse than the model can make it.
|
| 194 |
+
|
| 195 |
+
## Listening before generating
|
| 196 |
+
|
| 197 |
+
Before any of the model stuff, CODA reads your clip with plain librosa DSP β no ML,
|
| 198 |
+
nothing to download, runs in a blink:
|
| 199 |
+
|
| 200 |
+
- **Key** via KrumhanslβSchmuckler profile correlation over the chroma.
|
| 201 |
+
- **Tempo** from librosa's beat tracker.
|
| 202 |
+
- **Meter** by scoring how well a "downbeat every N beats" grid lines up with where
|
| 203 |
+
the accents actually land β 3/4 has to win clearly or it's called 4/4, like most
|
| 204 |
+
music is.
|
| 205 |
+
|
| 206 |
+
And because people's unfinished songs live on phone recordings and old MP3 rips,
|
| 207 |
+
there's a lo-fi **input enhancer**: a 35 Hz rumble filter and a spectral noise gate
|
| 208 |
+
clean a *copy* of your audio that feeds the analysis and the model, so it follows
|
| 209 |
+
the **song** and not the hiss. Your real recording is never touched by this β it
|
| 210 |
+
goes into the final track exactly as you played it. (Tick *remaster my part* if you
|
| 211 |
+
want the same cleanup applied to your section too, so the whole thing sits at one
|
| 212 |
+
level.)
|
| 213 |
+
|
| 214 |
+
## The frontend
|
| 215 |
+
|
| 216 |
+
I didn't want this to look like a default Gradio app, so it isn't one. It's a dark
|
| 217 |
+
"milled instrument" β backlit LCD readouts, engraved labels, faders with real
|
| 218 |
+
bevels, a gold flourish when your finished track lands. Most of the state lives in
|
| 219 |
+
CSS `:has()` selectors keyed off real DOM state, so the button lights while the
|
| 220 |
+
engine runs and the result panel goes gold on reveal with no JavaScript event
|
| 221 |
+
wiring at all.
|
| 222 |
+
|
| 223 |
+
The hero spectrum is my favorite piece. It's a real audio-reactive visualizer β
|
| 224 |
+
not a fake loop. When your finished track plays, the bars dance to **actual FFT
|
| 225 |
+
data** from a Web Audio `AnalyserNode`. Getting there was a fight: Gradio's
|
| 226 |
+
WaveSurfer plays from a decoded buffer through its own audio graph, so its
|
| 227 |
+
`<audio>` element never loads and there's nothing to tap. The trick is a **silent
|
| 228 |
+
shadow `<audio>`** of the same file, wired `MediaElementSource β AnalyserNode` but
|
| 229 |
+
deliberately *not* connected to the speakers β it gives me real frequency data
|
| 230 |
+
while staying inaudible, locked in step with WaveSurfer's play/pause and position.
|
| 231 |
+
Real bars, no echo.
|
| 232 |
+
|
| 233 |
+
There's also a one-time cinematic intro that tells the 2016 story, and a
|
| 234 |
+
server-rendered before/after ribbon under the player that marks exactly where your
|
| 235 |
+
part ends and CODA's begins β computed from the real numbers, so the marker can't
|
| 236 |
+
drift.
|
| 237 |
|
| 238 |
## The stack β small on purpose
|
| 239 |
|
| 240 |
| Component | Size | Job |
|
| 241 |
|---|---|---|
|
| 242 |
| [Stable Audio 3 Small Music](https://huggingface.co/stabilityai/stable-audio-3-small-music) | ~0.6B | native audio-inpaint continuation |
|
| 243 |
+
| T5Gemma (bundled with SA3) | ~0.5B | optional text steering for the vibe box |
|
| 244 |
+
| librosa + SciPy | 0 params | key/tempo/meter, lo-fi cleanup, scoring |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
|
| 246 |
+
The whole thing runs inside a single ZeroGPU window β generation is seconds, not
|
| 247 |
+
the whole budget β with no cloud APIs in the loop. 0.6B parameters doing
|
| 248 |
+
waveform-level music generation.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
+
## Running it
|
| 251 |
|
| 252 |
+
On the Space, just open it and press *Hear Track0000*, or drop in your own clip.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
+
Locally (needs Python 3.10 and a CUDA GPU; SA3's weights are gated, so accept the
|
| 255 |
+
license on the model page and `huggingface-cli login` first):
|
| 256 |
|
| 257 |
```bash
|
| 258 |
pip install -r requirements.txt
|
| 259 |
python app.py
|
| 260 |
```
|
| 261 |
|
| 262 |
+
Deploying your own copy: the SA3 weights are gated, so add an **`HF_TOKEN`** secret
|
| 263 |
+
(from an account that accepted the license) in the Space settings or the download
|
| 264 |
+
401s at startup. `stable-audio-tools` hard-pins `torch==2.7.1`, which ZeroGPU
|
| 265 |
+
rejects, so `app.py` installs it `--no-deps` at runtime and lets the
|
| 266 |
+
ZeroGPU-managed torch win β its non-torch dependencies are all in
|
| 267 |
+
`requirements.txt`.
|
| 268 |
|
| 269 |
+
## What it won't pretend to do
|
| 270 |
+
|
| 271 |
+
SA3 is a *music* model. The continuation leans instrumental, and CODA does not fake
|
| 272 |
+
vocals it can't generate. That's the honest design line: **your original plays
|
| 273 |
+
untouched up to the seam, vocals and all**, and the generated section carries the
|
| 274 |
+
music on from there. The band plays the outro; you can still go write the next
|
| 275 |
+
verse over it. Leave the vibe box empty for a faithful, audio-led continuation that
|
| 276 |
+
holds your key and tempo; type a vibe and you're choosing to let it steer
|
| 277 |
+
creatively, which can pull it off your exact key β that's the trade, and it's yours
|
| 278 |
+
to make.
|
| 279 |
+
|
| 280 |
+
## Where it's headed
|
| 281 |
|
| 282 |
+
The thing I want next is **vocal continuation** β letting the model carry a melody
|
| 283 |
+
line, not just the instrumental bed. After that, multiple continuation options side
|
| 284 |
+
by side so you can pick the direction instead of taking the cleanest draw, and a
|
| 285 |
+
"finish to a specific length and resolve on the tonic" mode so it lands like a real
|
| 286 |
+
ending instead of fading. The bones are here; the inpainting core makes all of it
|
| 287 |
+
reachable.
|
| 288 |
|
| 289 |
+
## Why I think it belongs in this hackathon
|
|
|
|
|
|
|
|
|
|
| 290 |
|
| 291 |
+
The brief was *build small*. CODA is 0.6B parameters doing something the big
|
| 292 |
+
text-to-music models mostly can't be bothered with β taking your actual recording
|
| 293 |
+
and continuing it at the sample level. As far as I can tell, of the entire field of
|
| 294 |
+
entries it's the only one doing waveform-level audio AI generation; everything else
|
| 295 |
+
in the audio category is text-to-music plus a TTS voice. Nobody else is touching
|
| 296 |
+
inpainting-based continuation. Small model, real DSP, a custom instrument for a
|
| 297 |
+
frontend, and a single job it does well.
|
| 298 |
+
|
| 299 |
+
It also gave a ten-year-old unfinished song an ending, which is the part I
|
| 300 |
+
actually care about.
|
| 301 |
|
| 302 |
---
|
| 303 |
|
| 304 |
+
Built by **Tony Winslow** Β· Black Box Analytics Β· for the Build Small Hackathon,
|
| 305 |
+
2026.
|
| 306 |
+
|
| 307 |
+
Code is MIT. SA3's weights are under the
|
| 308 |
+
[Stability AI Community License](https://stability.ai/license) (free for commercial
|
| 309 |
+
use under $1M annual revenue) and bundle a T5Gemma encoder under the Gemma Terms of
|
| 310 |
+
Use β worth knowing if you fork it.
|
| 311 |
+
|
| 312 |
+
**Demo clip:** `examples/track0000_tony_winslow.mp3` β my own song, recorded 2016,
|
| 313 |
+
never finished. The exact kind of clip CODA exists for: lo-fi in, finished-sounding
|
| 314 |
+
out. Bring your own and finish yours.
|
app.py
CHANGED
|
@@ -399,7 +399,7 @@ THEME = gr.themes.Base(
|
|
| 399 |
)
|
| 400 |
|
| 401 |
CSS = """
|
| 402 |
-
/*
|
| 403 |
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap');
|
| 404 |
|
| 405 |
:root{
|
|
@@ -1016,12 +1016,7 @@ footer{ display:none !important; }
|
|
| 1016 |
@media (prefers-reduced-motion: reduce){ .coda-drop .icon-wrap{ animation:none !important; } }
|
| 1017 |
|
| 1018 |
|
| 1019 |
-
|
| 1020 |
-
* APPEND ALL OF THIS to the END of the existing CSS string in app.py β i.e.
|
| 1021 |
-
* just BEFORE the closing triple-quote at line 925 (after the last @media block
|
| 1022 |
-
* at line 923). Purely additive; no existing selector is modified. No new font
|
| 1023 |
-
* import (Space Grotesk / Inter / JetBrains Mono already loaded).
|
| 1024 |
-
* ========================================================================== */
|
| 1025 |
|
| 1026 |
/* ===== FEATURE A β PLAY-ENERGIZED HERO ===== */
|
| 1027 |
/* When the player reports it's playing, the JS adds .coda-eq-playing and the
|
|
@@ -1403,7 +1398,7 @@ AMBIENT = ("<div class='coda-aurora'></div>"
|
|
| 1403 |
"<div class='coda-glow'></div>"
|
| 1404 |
f"<div class='coda-particles'>{_parts}</div>")
|
| 1405 |
|
| 1406 |
-
# Feature C β cinematic onboarding overlay.
|
| 1407 |
# EVERY page load. It is rendered with `coda-intro-on` BAKED IN, so the CSS beats
|
| 1408 |
# start immediately on load WITHOUT waiting on (or even needing) JS β bulletproof
|
| 1409 |
# against the head-injection issue that made it vanish on the Space. The
|
|
@@ -1495,7 +1490,7 @@ CODA_INIT_JS = """
|
|
| 1495 |
});
|
| 1496 |
|
| 1497 |
/* ===== FEATURE A β AUDIO-REACTIVE HERO (real FFT via a shadow <audio>) =====
|
| 1498 |
-
*
|
| 1499 |
*
|
| 1500 |
* Verified live: Gradio's WaveSurfer plays from a DECODED BUFFER through its
|
| 1501 |
* own Web Audio graph β its <audio> element never loads (readyState 0, no
|
|
@@ -1765,7 +1760,7 @@ CODA_INIT_JS = """
|
|
| 1765 |
host.setAttribute('aria-hidden', 'true'); host.remove();
|
| 1766 |
} catch (e) {}
|
| 1767 |
}
|
| 1768 |
-
/*
|
| 1769 |
* first. So there is NO localStorage "seen" gate β we always boot it.
|
| 1770 |
* Skip / Esc / click-outside / auto-advance still dismiss it per load. */
|
| 1771 |
var tries = 0;
|
|
|
|
| 399 |
)
|
| 400 |
|
| 401 |
CSS = """
|
| 402 |
+
/* CODA β premium dark-studio design system */
|
| 403 |
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap');
|
| 404 |
|
| 405 |
:root{
|
|
|
|
| 1016 |
@media (prefers-reduced-motion: reduce){ .coda-drop .icon-wrap{ animation:none !important; } }
|
| 1017 |
|
| 1018 |
|
| 1019 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1020 |
|
| 1021 |
/* ===== FEATURE A β PLAY-ENERGIZED HERO ===== */
|
| 1022 |
/* When the player reports it's playing, the JS adds .coda-eq-playing and the
|
|
|
|
| 1398 |
"<div class='coda-glow'></div>"
|
| 1399 |
f"<div class='coda-particles'>{_parts}</div>")
|
| 1400 |
|
| 1401 |
+
# Feature C β cinematic onboarding overlay. The 2016 story plays on
|
| 1402 |
# EVERY page load. It is rendered with `coda-intro-on` BAKED IN, so the CSS beats
|
| 1403 |
# start immediately on load WITHOUT waiting on (or even needing) JS β bulletproof
|
| 1404 |
# against the head-injection issue that made it vanish on the Space. The
|
|
|
|
| 1490 |
});
|
| 1491 |
|
| 1492 |
/* ===== FEATURE A β AUDIO-REACTIVE HERO (real FFT via a shadow <audio>) =====
|
| 1493 |
+
* The hero bars dance with the finished track, driven by real FFT data.
|
| 1494 |
*
|
| 1495 |
* Verified live: Gradio's WaveSurfer plays from a DECODED BUFFER through its
|
| 1496 |
* own Web Audio graph β its <audio> element never loads (readyState 0, no
|
|
|
|
| 1760 |
host.setAttribute('aria-hidden', 'true'); host.remove();
|
| 1761 |
} catch (e) {}
|
| 1762 |
}
|
| 1763 |
+
/* Design choice: the 2016 intro replays on EVERY page load, not just the
|
| 1764 |
* first. So there is NO localStorage "seen" gate β we always boot it.
|
| 1765 |
* Skip / Esc / click-outside / auto-advance still dismiss it per load. */
|
| 1766 |
var tries = 0;
|