Petri Nets as a Music Sequencer
Orchestrating Sound with Petri Nets
Explore the intersection of mathematical modeling and music at beats.bitwrap.io. By selecting from 19 different genres—ranging from ambient and jazz to techno and drum and bass—you can experience a fully realized track featuring melodies, arpeggios, basslines, and percussion.
The engine driving this experience is based on Petri nets: a system of circles (places) linked by arrows (transitions), where the movement of tokens dictates the music. In this system, every single note is represented by a token advancing one step through a graph.
🥁 A Token-Based Drum Machine
Imagine a circular arrangement of 8 places. The rhythmic feel is determined by the Bjorklund algorithm, which distributes hits across steps as uniformly as possible.
- The Tresillo: When you place 3 hits across 8 steps, you create a rhythm fundamental to everything from Rihanna's hits to Afro-Cuban son.
- Techno: Adjusting the parameters to 4 hits over 16 steps yields a classic "four-on-the-floor" beat.
- Polyrhythms: By utilizing rings of varying lengths, the patterns naturally drift in and out of alignment. For example, layering a 6-step hi-hat over a 16-step kick mimics the complex cross-rhythms found in West African music.
Visualizing the Flow
(In this model, the dot moving through the circles is the token.)
🎹 From Math to Music Theory
Beyond simple percussion, each step in the net carries specific musical data: pitch, velocity, and duration. The system applies music theory to ensure the output sounds cohesive:
- Strong beats are assigned chord tones.
- Weak beats utilize passing tones.
- Rests are inserted to allow the composition to "breathe."
Depending on the chosen genre, presets automatically configure the following:
- Scales and Chords
- Drum patterns
- Swing amount
- Humanization factors
Note: There is no AI or sample library here. The music is generated using pure music theory and seeded random numbers.
🎛️ Managing Structure: Control Nets
While loops are simple, creating a full song requires a "stage director." This is achieved via control nets—rings that trigger commands (e.g., mute_hihat or start_bass) rather than audible notes.
The Optimization Journey
The initial architecture was highly inefficient "hilariously wasteful," utilizing a linear chain of 1,536 steps to track time. To optimize this, the developer replaced long chains with a single place containing multiple tokens that "drain" one per tick. An inhibitor arc is used to prevent a control event from firing until the token count reaches zero.
Efficiency Gains:
| Metric | Original Version | Optimized Version |
|---|---|---|
| Places | 37,148 | 800 |
| Transitions | 37,120 | 800 |
| File Size | 22 MB | 242 KB |
🎧 The Auto-DJ and Synchronization
To ensure seamless transitions between tracks, the "Auto-DJ" must time filter sweeps or risers perfectly with the downbeat. Early attempts to sync main-thread timers with worker-side regeneration led to race conditions and dropped macros.
The solution was to treat the transition itself as a Petri net component:
- A one-transition control net is injected.
t0fires on the first tick of the new track.- The worker emits a
control-firedsignal. - The main thread processes the macro via the standard queue.
Because the Petri net executor is the scheduler, the timing is synchronized by design.
🛠️ Technical Architecture & Benefits
By using a single primitive for concurrency and state, several advantages emerged:
- Determinism: The token state is the only state. Using the same seed guarantees the same track across all devices.
- Observability: The net can be rendered visually. If a sound is wrong, you can literally see a token stuck in the wrong place.
- Modularity: Instruments and song structures exist on separate layers; adding or removing one does not disrupt the others.
The Stack
The project is remarkably lightweight:
- Audio Engine: Tone.js (handles polyphonic synths, reverb, and sub-millisecond scheduling).
- Executor: ~400 lines of vanilla JavaScript.
- Sequencer Worker: ~700 lines of vanilla JavaScript.
- Backend: A Go server serving static files.
For those interested in the formal logic of token flow as a computational model, refer to Declarative Differential Models. The full source code is available at github.com/stackdump/beats-bitwrap-io.