The blog post Petri Nets as a Music Sequencer hit Hacker News (discussion, 66 pts / 21 comments) and drove ~1.4k sessions in a week — roughly 60% of them on phones (nginx UA split: ~15.5k Android + ~13.2k iPhone requests vs ~18.6k desktop).
Several commenters reported broken audio: no sound at all, "disorienting delay" on kick hits, and triggers visibly firing at the wrong token position. These symptoms match mobile audio-thread underruns, not scheduler bugs — the worker tick loop + 80 ms lookahead architecture is sound. The DSP load is the problem:
Proposed fixes (ranked)
- Bypass idle master-chain FX. Every sample currently passes through PitchShift (phase vocoder!) → Distortion → BitCrusher → LP(-48 dB) → Phaser → HP(-48 dB) → Compressor plus Freeverb/FeedbackDelay buses even at
wet: 0 (public/audio/tone-engine.js ~1338–1360). Tone's wet/dry crossfade still runs the effect DSP at wet 0. Disconnect and hard-wire around idle effects until a macro raises wet; take care the reconnect doesn't click mid-ramp.
- Set
latencyHint: 'playback' on mobile. No latencyHint is set anywhere, so the context runs at interactive (~128-sample callbacks). This is a generative player, not a live instrument — playback buys 4–8× per-callback headroom at no perceptible cost. Companion change: raise LOOKAHEAD_MS (80 → ~150–200) on mobile (public/sequencer-worker.js, public/lib/backend/index.js).
- Mobile voice budget.
maxPolyphony = 256 per PolySynth (~15 instrument defs, tone-engine.js ~1655) lets busy sections exceed what a phone's audio thread can render before the buffer deadline. Cap at 8–16 per instrument on mobile; the voice-recycle machinery from the oscillator-leak fix is there to build on.
- Throttle visuals on phones. SVG token/grid updates fire per worker message on the main thread; coalesce to rAF and skip offscreen nets under
max-width: 720px.
Verification
Use the CDP profiling harness in ~/Workspace/beats-profile/ with Chrome CPU throttling (4–6×) to simulate a phone; success = no underruns/crackle through a full Auto-DJ cycle.
Not fixable here: bottom-tier Androids with 8 parallel synth tracks, and the iOS tap-to-start gesture requirement.
The blog post Petri Nets as a Music Sequencer hit Hacker News (discussion, 66 pts / 21 comments) and drove ~1.4k sessions in a week — roughly 60% of them on phones (nginx UA split: ~15.5k Android + ~13.2k iPhone requests vs ~18.6k desktop).
Several commenters reported broken audio: no sound at all, "disorienting delay" on kick hits, and triggers visibly firing at the wrong token position. These symptoms match mobile audio-thread underruns, not scheduler bugs — the worker tick loop + 80 ms lookahead architecture is sound. The DSP load is the problem:
Proposed fixes (ranked)
wet: 0(public/audio/tone-engine.js~1338–1360). Tone's wet/dry crossfade still runs the effect DSP at wet 0. Disconnect and hard-wire around idle effects until a macro raises wet; take care the reconnect doesn't click mid-ramp.latencyHint: 'playback'on mobile. No latencyHint is set anywhere, so the context runs atinteractive(~128-sample callbacks). This is a generative player, not a live instrument —playbackbuys 4–8× per-callback headroom at no perceptible cost. Companion change: raiseLOOKAHEAD_MS(80 → ~150–200) on mobile (public/sequencer-worker.js,public/lib/backend/index.js).maxPolyphony = 256per PolySynth (~15 instrument defs,tone-engine.js~1655) lets busy sections exceed what a phone's audio thread can render before the buffer deadline. Cap at 8–16 per instrument on mobile; the voice-recycle machinery from the oscillator-leak fix is there to build on.max-width: 720px.Verification
Use the CDP profiling harness in
~/Workspace/beats-profile/with Chrome CPU throttling (4–6×) to simulate a phone; success = no underruns/crackle through a full Auto-DJ cycle.Not fixable here: bottom-tier Androids with 8 parallel synth tracks, and the iOS tap-to-start gesture requirement.