Scripting a rev sweep with suspend and resume

The ten-second clip on every engine page is rendered by an OfflineAudioContext, which does not advance on its own and has no wall clock to schedule changes against. dev/offline/renderPreviews.ts drives it by suspending and resuming the render sixty times a second, stepping a scripted rev sweep rather than holding one rpm.
A context that renders but does not tick
The AudioContext that drives EV·ENGINE while a car is moving has a wall clock: it plays in real time, sample by sample, timed to whatever the browser's audio hardware asks for next. An OfflineAudioContext has none of that. Ask it to render ten seconds of audio and it renders ten seconds of audio as fast as the machine allows, with no clock a script could use to schedule a change partway through.
The only way to touch a parameter mid-render is to ask the context to stop at a given rendered time and hand control back to ordinary JavaScript before it continues. suspend(time) returns a promise that resolves once rendering reaches that point; resume() lets the render carry on. dev/offline/renderPreviews.ts is built entirely on that pair, calling suspend and resume in a loop to produce the ten-second clip on every engine page.
Sixty steps to move a needle
The loop steps at 60 Hz: every sixtieth of a second of rendered audio, the script suspends, updates the virtual throttle and the rpm the gearbox is reporting, then resumes. Across a ten-second clip that is six hundred suspend and resume pairs, each one a chance to move the operating point somewhere a live AudioWorklet never has to think about, because there the browser's own clock is already doing the stepping.
Two markers sit inside that ten seconds. The sweep launches from idle at one second in, and it lifts off at 8.3 seconds, leaving the last 1.7 seconds of the clip to render the overrun. On a preset where lifting off matters, such as the turbo hot hatch, that is the moment the overrun pop probability gets a chance to fire before the file runs out.
Why a held rpm was the wrong shape
An earlier version of these clips held one rpm for the full ten seconds. It is a defensible way to render a file: pick an operating point, run the synthesis engine at it, capture the output. It is also the wrong shape for a preview whose job is to let someone hear what an engine is, because most of what separates one preset from another only shows up while rpm is moving. A shift landing, a growl building as revs climb, an overrun catching or not catching, none of that exists at a single fixed point.
The gearbox itself only misbehaves while it is changing state, and the same logic applies to how an engine reveals itself: the interesting behaviour lives in the transition, not the hold. The muscle V8 preset carries a firing unevenness of 38, and that lope only becomes obvious as revs climb through a gear rather than sit in the middle of one; a clip held at 3,000 rpm for ten seconds would say almost nothing about it.
One script, twenty engines, one loudness pass
The same suspend and resume loop renders all twenty preset clips, one file per engine, with the same launch and lift timing in every case. That consistency matters: whoever is comparing engines is hearing two runs through the same throttle script rather than two arbitrary snapshots someone picked by ear.
Because each clip is rendered once and never in real time, the loudness trim described in matching loudness across twenty engines can be baked into the file itself rather than applied live. The same rpm and gearbox model that responds to GPS speed while the car is moving, laid out in how it works, is what the script is driving during the render; only the input differs, a scripted timeline instead of a phone's own idea of speed.
Suspend and resume look like a workaround the first time you meet them, a way of forcing synchronous code into an asynchronous render. They are the API working as designed: an OfflineAudioContext has no clock of its own to interrupt, so the only way to touch it mid-flight is to ask it to stop and wait. Sixty times a second, for six hundred pairs, that is what turns a single held operating point into ten seconds that sound like an engine being driven.