Go to beats.bitwrap.io. Pick a genre — techno, jazz, ambient, drum & bass, 19 in total. Hit Generate. You'll hear a full track: drums, bass, melody, arpeggios. Change the seed, get a different track. Replay the same seed, get the exact same one.
There's no piano roll behind this. No timeline grid, no DAW. The whole thing runs on Petri nets — circles connected by arrows, with tokens moving through them. Every note is literally a token moving one step through the diagram.
Picture a ring of 8 circles connected by arrows. One dot — a token — sits at the first circle. Every tick, it hops to the next one. Some circles trigger a kick drum when the token lands. The rest are silent. The token goes around and around. You've got a beat.
The pattern of hits comes from the Bjorklund algorithm — it spreads K hits across N steps as evenly as possible. With 3 hits across 8 steps you get the tresillo, a rhythm that shows up everywhere from Afro-Cuban son to Rihanna.
That ring of circles? It's called a Petri net. The circles are places, the arrows pass through transitions, and the dot is a token. That's the whole vocabulary. Change the numbers — 4 hits across 16 steps — and you've got four-on-the-floor techno. Same structure, different rhythm.
Each instrument gets its own ring. Kick, snare, hihat, clap — all running in parallel. Make the rings different lengths and they drift in and out of sync, giving you polyrhythm for free. A 6-step hihat over a 16-step kick sounds like the cross-rhythms in Afro-Cuban and West African music, because it is the same math.
Melodies work the same way. Instead of drum hits, each step carries a note — pitch, velocity, duration. The music theory picks notes that make sense over the chord (chord tones on strong beats, passing tones on weak beats, rests so it can breathe), and lays them out in a ring.
Bass lines? Ring. Arpeggios? Ring. Harmony pads? You guessed it.
Pick a genre — techno, jazz, ambient, drum & bass — and a preset fills in the details: scales, chords, drum patterns, how much swing, how much humanize. The generator assembles a bunch of rings, and the sequencer just... runs them. No AI, no samples — music theory and random numbers with a seed. Same seed, same track, every time.
Loops are easy — tokens go around, patterns repeat. But a song needs intros, drops, breakdowns. The hihat should disappear for 8 bars, then come back. The bass should sneak in during the second verse.
So we added control nets — rings that don't make sound but fire commands like "mute the hihat" or "bring in the bass." A tiny stage director made of circles and arrows.
The first version was hilariously wasteful: a chain of 1,536 individual steps, one per tick, with a command every few hundred steps and nothing in between. It worked! But the project file was 22 MB for a 3-minute track.
The fix: countdown timers. Instead of 384 empty steps before a section change, we put 384 tokens in a single circle and drain one per tick. A special connection (called an inhibitor arc) holds back the control event until the countdown hits zero. Same result, way less diagram:
| Before | After | |
|---|---|---|
| Places | 37,148 | 800 |
| Transitions | 37,120 | 800 |
| File size | 22 MB | 242 KB |
One circle with many tokens does the job of many circles with one token each. Petri nets are flexible like that.
Auto-DJ regenerates a new track at the boundary, and we wanted a curated filter sweep, white-noise wash, or riser to land on the downbeat — not race the project swap from the main thread.
The first attempt coordinated main-thread timers against worker-side regen state. It mostly worked, except when a user gesture overlapped the boundary and the macro got silently dropped. We were fighting the runtime.
The fix was to stop treating transitions as a side channel. Each Auto-DJ transition is now injected as a one-transition control net with a fire-macro binding. t0 fires on tick 1 of the new track; the worker emits control-fired; the main thread runs the macro through the normal queue. Tick-synchronized by construction, because the Petri net executor is already the scheduler.
Two timing bugs — the project-swap race and the macro-queue drop — collapsed into one piece of structure. The transition is just another token moving through the graph. Details in the v1.1.0 release notes; the curated transition pool itself shipped in v1.0.8.
The token state is the only state, so the same seed produces the same track on any machine — no hidden counters, no drift. The same diagram that runs the music can be drawn on screen and watched: when something sounds wrong, we can see a token stuck in the wrong place or a ring out of phase. And because each instrument is its own net and song structure is a separate layer, pulling one out or adding another doesn't break anything else. None of this took extra work; it's just what falls out of having a single primitive carry concurrency, state, and scheduling.
beats.bitwrap.io — 19 genres, all client-side, nothing to install.
A huge shoutout to Tone.js, which does all the heavy lifting on the audio side. Petri nets decide what plays and when — Tone.js makes it actually sound good. Polyphonic synths, reverb, compression, sub-millisecond scheduling — we got all of that essentially for free. It's one of those libraries where you keep expecting to hit a wall and never do.
The Petri net executor is ~400 lines of vanilla JS, with another ~700 in the sequencer worker. The rest — music theory, generators, UI — is bigger, but none of it is framework code. No npm, no bundler. Tone.js from CDN, a Go server that serves static files, and a 60-year-old idea about circles and arrows that turned out to be a pretty good sequencer.
Source: github.com/stackdump/beats-bitwrap-io
For the formal theory behind why token flow works as a computational model, see Declarative Differential Models. To build and simulate your own nets, try pflow.xyz.