The code is at https://github.com/machmoon/oasis (branch sound-ui), and the demo video above walks the whole product. To run it: npm install, then npm start, then open http://localhost:8787 (PayPal sandbox; set the keys in .env).
Inspiration
A game needs three hundred footsteps that match. Today it gets three hundred copies of one file, or a pack of forty files and a random picker. The person who designed the footstep was paid once, when the pack sold, if at all.
We wanted to sell the program, not the file. A footstep has a surface, a weight, a pace, a wetness and a seed. Render it at the call site and every take is new. License it per program and the creator is paid from every order that includes it.
Two projects shaped the rest. Polyfork sells 3D models as little programs with knobs, and its write-up is the structure this one follows. Crate's pitch for its AI kit was "describe the vibe you're going for and get straight to playing" (devpost.com/software/crate-iphone-duo-mpc). We took that line literally: one sentence in, a kit of tuned sound programs out, paid for in one PayPal order.
What it does
Every sound is a program. footstep.mjs exports meta (title, kind, price, creator), params (typed knobs with a required seed) and build(knobs, ctx), which returns PCM samples from pure math over a seeded PRNG. No Web Audio, no Math.random; it throws in the sandbox. The same knobs give the same samples on the server, in a browser Worker and in a licensed import. There are 265 programs in the registry right now, 18,175 lines of code between them, with 1,656 knobs (282 choice, 1174 range, 200 toggle), priced $1 to $5. 236 of them were built and graded by the factory, 5 were the hand-written seed set, and 24 (the Instrument and Interface collections) were written by hand by a Claude Code agent against the same contract and harness while the factory's API credit was out.
A different seed is a different take. The sound page walks 300 seeds of your knobs along one timeline. Measured on the footstep at gravel, weight 0.8: centroid 557 to 728 Hz, rms 0.152 to 0.219, 294 of 300 takes distinct by their numbers.
Browse, turn, listen. The catalogue shows every program's waveform at its defaults, filtered by kind, collection, price and creator. Open one and every knob is a rotary dial. Turn weight and the program runs again: the waveform's old bars glide into the new ones, the Roseus spectrogram and the timeline redraw under it from the new take, and a live spectrum and scope run while it plays. The import line updates with the knobs at the call site. Until a program is licensed, every preview carries a soft 2.4 kHz tick every 0.6 s.
A collection you can play. Every voice in the Instrument collection has a note knob, so its 12 voices (plucked string, electric piano, acid bass, choir and the rest) make a small synth: pick a note, a seed and the voice's own knobs, and the program renders that note. Each voice's page and the kit page put a piano keyboard on top of that: the page renders every note the voice offers in the background, so a key press, a drag across the keys or a chord on the computer keyboard plays at once.
Describe the vibe, get a kit. Type "rainy cyberpunk alley footsteps and UI clicks". Claude (claude-sonnet-5-5) reads the registry, picks up to ten programs, tunes each one's knobs to the scene, gives each part a seed and a name, and writes one line on why it is there. A second part on the same program is covered by the first, so a kit charges each program once. When the model is unavailable, a keyword planner builds the kit instead (whole-word matches, knobs turned when the vibe uses an adjective it knows, such as heavy, wet, distant or stormy; it names the words the registry has nothing for), and the kit says which planner made it.
Then play it. Every kit plays on pads (/#/pads/{kit}), an MPC-style grid of pads, one per part (up to 16) after CRATE, the iPhone Duo MPC that won Bitrig Hacks: a 16-step sequencer, eight styles, swing, a hinge fader whose fast pull-down drops the beat, and a typed prompt for "boom bap, bpm 96, take out the hats". Each pad strike plays a fresh seeded take, so a pattern cycles through four rendered takes per pad. The kit page also hands over a game pack: 8 WAV takes per sound with each take's peak and RMS, the import lines, and a license file.
Pay with PayPal, every part turns clean. A kit is one PayPal Orders v2 order with an itemised line per program. The server prices it; a client-supplied price is ignored. On capture each creator's share is booked (90 cents of every dollar to the creator, 10 to Oasis) and every part on the kit page switches from the watermarked preview to a clean WAV and a licensed module URL. Shares go out through PayPal Payouts after the 14-day refund window; a refund inside the window revokes the licences and cancels the payout.
A kit page plays the whole kit as one transport, part after part, with the playing part lit and its playhead moving. Copy link puts the kit's URL on the clipboard in one line.
The module URL is the product. import { play } from ".../cdn/footstep.mjs?lic=…" and call play(audioContext, { surface: "gravel", weight: 0.8, seed: 42 }). A browser without a licence gets a placeholder tick of the real length. Every other client gets HTTP 402 with headers shaped after x402 v2 (not spec-valid: the network, payTo and mandate-token proof are ours): scheme exact, network paypal:sandbox, amount 300 cents, payTo foleyroom.
Anyone can publish a sound. Paste a program written to the contract and press Check. It is a dry run, the way npm publish --dry-run is: the sandbox loads it, the factory's own harness measures it across its knob space, and the page shows the render, every gate and a play button per knob before anything is written. Publish runs the same check again on exactly that text and lists it under your name. Your creator page is what the ledger owes you, order by order, and when PayPal Payouts sends each share.
It works on a phone. At 390 px the nav keeps Sounds, Kits and Publish inline and puts the rest behind a More menu, kit parts stack, and every page keeps a 16 px gutter.
Agents run the same loop. claude mcp add --transport http oasis http://localhost:8787/mcp gives an agent seven tools: search_assets, get_asset, preview_asset (the WAV, a waveform and spectrogram card, and the numbers), buy_assets, make_kit, get_kit, get_budget. A human approves a budget once in PayPal (Vault), the server holds the cap, and an order over it is refused before PayPal is called.
How we built it
The contract and the DSP kit. factory/CONTRACT-SOUND.md is the whole rule: one module, three exports, a seed knob, no imports, no randomness outside ctx.rng. public/sound-dsp.js is what build gets: a seeded PRNG, RBJ cookbook biquads, pink and brown noise ported from Tone.js's Noise.ts, Karplus-Strong as Tone.js's PluckSynth.ts builds it, the envelope and sweep stages of jsfxr's sfxr.js, a Schroeder reverb, and the layering idiom from Farnell's Designing Sound (a body, a contact layer, air).
Renders on the server. server/sandbox.js runs a program in QuickJS with two host modules and nothing else importable, a 6 s clock and 48 MB of memory. server/sound.js turns samples into a WAV, a waveform PNG, a spectrogram PNG, a catalogue card, the measured numbers (seconds, peak, rms, centroid, silence, clipping) and a per-seed walk on a worker pool. Interpreted DSP is slow, so previews render at 22.05 kHz and free programs render in a browser Worker instead.
The factory. factory/factory-sound.mjs planned 12 kits (Rainy City Street, Wooden Tavern, Sci-fi Console, Forest at Night, Kitchen, Retro Arcade, Office, Car Interior, Medieval Market, Drum Machine, Ocean Harbour, Horror House) as 358 briefs (314 of them reached the grader before the API credit ran out), one Claude call per kit. For each brief, Claude (claude-opus-5-5) writes the program inside the kit's room and a creator's voice; the sandbox proves it renders; Claude reviews its own renders as pictures with the numbers; factory/harness-sound.mjs measures it across its knob space (length, peak, rms, silence, clipping, an end click, every knob's audible effect, seed distinctness by waveform correlation, render ms) and sends failures back; and an independent grader (factory/grader-sound.mjs, a fresh session) listens through the cards and scores realism, range, variation and knob design. The bar is the scores, not the grader's verdict word: every score at least 5, average at least 6.5. Survivors are published to sounds/ under one of five seed creators (their payout addresses are placeholders, so their shares are held). Every tally in this section counts the factory's builds only.
Two collections by hand. When the factory's API credit ran out, a Claude Code agent wrote the Instrument and Interface collections (24 programs) by hand against factory/CONTRACT-SOUND.md, and each one passes the same harness and the same tests as the factory's. They are not in factory/stats.jsonl and were not scored by the grader. Each file's header says it was hand-written and names the open-source synth code it follows: STK's Plucked, TubeBell, Rhodey, BeeThree, ModalBar, VoicForm and ADSR, Tone.js's MonoSynth, Chorus and FatOscillator, Open303's TB-303 filter, and jsfxr's sfxr.js, whose presets every interface sound starts from.
PayPal. Orders v2 through @paypal/paypal-server-sdk with itemised DIGITAL_GOODS lines, following PayPal's reference server (docs-examples/standard-integration/server/node/server.js). Capture happens on the server only, coalesced per order, and licences are issued only when the captured amount and currency equal what the server priced; a mismatch is refunded automatically. PayPal-Request-Id on create, capture and refund. Webhooks are verified with verify-webhook-signature before anything is trusted. Agent budgets are Vault v3 setup tokens shaped after the IntentMandate in Google's AP2 (AP2/code/sdk/python/ap2/models/mandate.py), with a server-held cap added because max_total_usd alone is only the agent's word. The 402 follows coinbase/x402 v2's HTTP transport headers. The whole path is in PAYPAL.md. The ledger counts every captured order; it used to count only agent orders on a budget, so the first kit checkouts were missing from it. It now shows 2 sandbox orders and $40.50 to creators.
The sound UI. Every waveform, spectrogram and timeline on the site is wavesurfer.js 7 (Spectrogram, Timeline, Minimap and Regions plugins). The morph is a wavesurfer renderFunction driven by a Motion spring. The live view reads an AnalyserNode the way wavesurfer's Record plugin does. The dials are ported from webaudio-controls and drawn with d3-shape. The 300-take walk is a d3 field over a zoomed waveform.
The pivot from design assets to sound is about 200 commits, from 22:40 on 4 October to the early morning of 6 October: 62 on sound-all (the sound branch with the UI, publish, ledger, kit and phone branches merged in), then about 130 on sound-ui (the pads, the game pack, buyer-only licence files, and a night of fixes from a panel of critic agents).
Challenges we ran into
Grading sound without ears. The grader sees pictures and numbers, not audio. Left to its own verdict word it rejected nearly everything, so the bar became the scores (every score at least 5, average at least 6.5) on top of a harness that has already proven the program renders, every knob matters and seeds differ. It still sent back 87 of 367 builds, and the lesson it writes for each one is the only memory the factory has between builds.
Interpreted DSP. A per-sample loop in QuickJS is 50 to 100 times slower than V8. The sandbox clock is 6 s, the pool's 8 s, previews render at 22.05 kHz, and free programs render in a browser Worker. The slowest published program is Undergrowth Push at 1035 ms for 1.18 s of audio.
Measuring on a busy laptop. The factory and the tests ran on one machine that was also doing other work, and under that load the sandbox clock interrupted renders that take a few hundred ms on a quiet machine. 44 factory builds errored before a verdict, and the numbers script now retries an interrupted render once instead of dying. The render times on this page were measured later, with the machine quiet: a median load average of 3.1 (peak 3.5) on 10 cores, 1 retry and 0 skipped. Just before this run the DSP kit's inner loops were rewritten for speed; in that change's own before-and-after benchmark the slowest render went from 3.56 s to 1.99 s and total render CPU fell 31%, and no sample changed by more than 6e-8.
Kits that charge a program twice. The first paid kit, Neon Rain Alley, billed three footsteps as three lines ($23.00 for 9 parts from 5 programs). Now a second part on the same program is marked covered and costs nothing; the same vibe prices at $11 to $14.
Autoplay. Sound needs a gesture. The home hero plays muted with an unmute pill over the picture, and sound starts on the first press.
Determinism across three runtimes. The same knobs must give the same samples in QuickJS, in a browser Worker and in a licensed import. Math.random throws in the sandbox; the only randomness is ctx.rng(seed), and a test checks the samples match.
Accomplishments that we're proud of
- A real PayPal sandbox order for a kit, created by the kit page's own checkout: 97H109249N081403K, $22.00 for the Hearthside Tavern kit, captured as 2SJ84518V9868791E and split across three creators (hollowbody $15.30, stormfront $3.60, quietmachine $0.90); its repeated program is covered, so each program is charged once. Approval used one of PayPal's published sandbox test cards instead of a person in PayPal's window; capture, the amount check and the licences are the normal code path. An earlier order, 9SS52993P3394003X ($23.00), predates charge-once pricing and billed the footstep and click programs three times each; the code to refund those $8 of repeated lines (from the overbilled creators' shares only) is written and tested, and waits on a person to run it.
- 265 sound programs by 5 creators, 260 of them in 14 collections: 236 built and graded by the factory, whose harness and grader rejected 87 more of its 367 brief runs (548 graded attempts, 306 sent back); 5 written by hand as the seed set; and 24 (the Instrument and Interface collections) written by hand by a Claude Code agent against the same contract and harness while the factory's API credit was out.
- 72 tests: the sandbox refusing
Math.random, imports and over-long renders; determinism; the watermark; the CDN gate; publish and creator pages; and a kit order through checkout, capture and claim. On the last run all 72 passed, including the two that render every one of the 265 programs across its knob space, after the DSP kit's inner loops were made about a third faster with the samples unchanged. - An agent can do all of it over MCP with none of our UI: search in 4 ms, preview in 26 ms, a planned kit in 11,529 ms.
- Every claim on this page is a number read from the repo, the stats file or the running server.
What we learned
- "Watermark until paid" works for sound. A soft tick on every preview that lifts the moment the order lands says "this was paid for" faster than a receipt.
- For agentic commerce the right primitive is not "the agent has a card". The human approves a budget once in PayPal, the server enforces it, and every creator's share is booked from the same order.
- A grader that only sees pictures needs a measured harness under it. Numbers catch the dead knobs and the silent tails; the grader catches the mug that sloshes louder than it lands.
- The lessons file beat prompt engineering. 564 short sentences from the grader, read before every build, moved the publish rate more than any change to the system prompt.
- Sell the program and the catalogue stops being a list of files. 300 footsteps from one $3 licence is a different product from a 40-file pack.
What's next for Oasis
- The 122 briefs that did not make it, regraded against today's 564 lessons. And creators who are people with real payout addresses, not placeholders: the publish page is ready for them.
- PayPal Commerce Platform multiparty settlement, so the split happens at capture with
payeeper purchase unit andplatform_fees, and refunds unwind it automatically. - Stereo, longer loops, and a
play()that schedules a whole kit on one AudioContext. - An agent that files what wasted its time, the way Polyfork's does.
Built With
- anthropic
- claude
- express.js
- model-context-protocol
- node.js
- paypal
- paypal-orders-api
- paypal-payouts
- paypal-vault
- quickjs
- render
- wavesurfer.js
- web-audio-api
- webassembly
Log in or sign up for Devpost to join the conversation.