EV·ENGINESTART THE SIMULATOR

Why the engine runs in an AudioWorklet

A sound wave racing along a conveyor toward a rhythmically closing gate, with a thinner wave lagging on a second track behind it.

EV·ENGINE's engine sound is generated inside an AudioWorklet, code running on its own audio thread that must hand back a finished slab of samples every few milliseconds without waiting for the browser's main thread to answer. When that thread cannot be created, the sound falls back to a thinner synthesiser, and the offline tools refuse to measure it.

A deadline the main thread cannot keep

AudioWorkletProcessor runs on the browser's dedicated audio rendering thread, a thread the page's ordinary JavaScript never touches. Every render quantum, a fixed block of samples, the thread has to hand back a complete slice of audio or the output glitches. That block is worth only a few milliseconds, which is not enough time to post a message to the main thread and wait for whatever else that thread happens to be doing: laying out a DOM update, running garbage collection, waiting on a fetch. Miss that budget once and the click is audible as a dropped sample; miss it repeatedly and the engine's note turns to static.

That is the reason the whole of EV·ENGINE's engine model lives inside a single file, src/audio/classic/classic-processor.ts, rather than being split between the page and the audio thread with values passed back and forth. Speed and rpm are read on the main thread and handed down once, but from there gear selection, the order bank, the comb filter and the noise floor are all computed inside the worklet, render quantum after render quantum, with nothing to wait on. Only a browser offering a secure context, Geolocation, Web Audio and AudioWorklet together can host it.

What the worklet builds

Inside that thread sits a 14-partial order bank running alongside a per-cylinder pulse train, the pairing that turns a gear-derived rpm into a waveform every render quantum. The order bank is what keeps an engine's timbre recognisable across its whole rev range, rather than the thin, pitched-up whine a single recording produces once it has been stretched three octaves from where it was captured.

Firing order sets pulses, the number of firings per revolution, and pulses sets the fundamental the order bank has to shape. The reference four-cylinder fires twice a revolution; the V12 fires six times. Building both from the same 14 partials, at whatever rpm the gearbox is turning over, inside the same render quantum, is the job the worklet does for all twenty machines without exception.

What plays when the worklet cannot load

Some browsers never grant a page an AudioWorkletGlobalScope: older WebViews, insecure contexts, engines that shipped Web Audio before finishing the worklet specification. When that happens ClassicEngine does not go silent. It falls back to an additive stack built from oscillator nodes running on the main thread instead of the audio thread. It still plays the right note at the right rpm, because gear selection and rpm calculation sit outside the worklet, but it loses the 14-partial order bank and the per-cylinder pulse train, so the fallback sounds thinner on every one of the twenty presets, not only the loudest.

There is no reduced order bank held in reserve for this case, only the full model or the substitute. That distinction rarely matters on a phone or a laptop, but it matters on a car's own head unit, where the browser is fixed and cannot be upgraded by the person driving it. Anyone who wants to know which one their browser is running can check signal-watcher's AudioWorklet check, which reports the support directly instead of leaving it to the ear.

Why the fallback is never measured

The offline renderer that produces EV·ENGINE's ten-second preview clips and sets its per-preset loudness trims runs against the worklet's output, because that is what everyone with the page open hears. Measuring the additive fallback instead would still produce a loudness figure and a peak level, only both would describe an instrument nobody chose and almost nobody will ever hit.

The same logic sits behind the soft clipper on the master bus, tuned against the worklet's peaks rather than the fallback's. A number attached to the wrong signal path is worse than no number at all, so the offline tools check for the worklet before rendering anything and stop rather than quietly measuring the substitute.

The fallback exists so the page never goes silent on a browser that cannot host a worklet, but it is a floor, not a target. Every figure published about EV·ENGINE's engines, from firing unevenness to comb filter feedback, describes the code running inside classic-processor.ts, on its own thread, meeting its render-quantum deadline without help from the page around it. A number measured off the fallback would describe a different, quieter engine, and the site would rather publish nothing than publish that.

More notes

All notes