# EngineVoice
The sound of a car. Put it on a car's chassis entity, the one that carries the driving contract's `car.*` named values, and it plays the engine, gear changes, rev limiter, turbo, tyre skids and crashes from those values. It needs no sibling component: any entity that writes `car.rpm`, `car.load` and the rest gets a voice, a `torqueCars` car, another car kit, or the `EngineVoiceDemo` stand-in.
It spawns local child entities, each with a spatial `Audio` source at `doppler = 1`, so passing cars drop in pitch:
- six engine layers, three rpm bands times on-load and off-load. They crossfade by rpm (equal power, in log rpm) and by `car.load`, and each is pitched to `rpm / band reference rpm`;
- an aux layer: the turbo whistle that follows `car.boost` (turbo, diesel) or the inverter whine (electric);
- a skid loop whose volume follows `car.skid` and whose clip changes with `car.surfaceId`: tarmac squeal, gravel crunch, soft scrub;
- a one-shot host for gear changes (`car.shiftCount`), overrun pops, blow-off on lift, limiter stutter pops and crash crunches (`car.impactCount`, scaled by `car.impactSpeed`).
The values replicate and the component runs on every peer, so every player hears every car.
## Fields
| field | default | meaning |
|---|---|---|
| `voice` | `auto` | `auto` follows `car.voiceId`. Set a name to force a voice: `four`, `v8`, `v12`, `diesel`, `rotary`, `turbo`, `electric`, `kart`. It can be changed live. |
| `volume` | 1 | multiplier for everything this car sounds |
| `maxDistance` | 160 | metres to silence |
| `shiftStyle` | `auto` | `auto` reads the `Car` component's gearbox mode, falling back to the voice's default; or `manual`, `sequential`, `automatic`, `none` |
| `skids`, `crashes` | true | turn those sounds off |
| `doppler` | 1 | Doppler scale on every source |
`:mix()` returns what the voice is doing this frame: the voice, the smoothed rpm, load and boost, the band weights, the on-load and off-load gains, the level, the skid level and kind, and the ids of its sources. Read those ids with `audio.voice(id)`.
```lua
car.component.add("EngineVoice") -- voice from car.voiceId
car.component.get("EngineVoice").voice = "rotary" -- switch it live
print(car.component.get("EngineVoice"):mix().bands[2])
```
# EngineVoiceDemo
A stand-in car for hearing `engineVoices` without a car. It writes the driving contract's `car.*` named values on its own entity, the same way a real car does. Each run idles, pulls through the gears at full throttle (shift sounds, rpm drops), stutters on the rev limiter, lifts off (overrun pops, blow-off) and slides across tarmac, gravel and grass, then ends in a crash. With `voice = "cycle"` it moves to the next of the eight voices after every run. With `flyby = true` it also drives back and forth past `center` at `speed` m/s, so you hear the Doppler drop.
Put an `EngineVoice` on the same entity. If the scene has no `AudioListener`, the demo puts one on the active camera so the sound is heard (turn that off with `listener = false`).
```lua
local e = entity.spawn("demoCar")
e.position = { 0, 1, -12 }
e.component.add("EngineVoiceDemo", { voice = "cycle" })
e.component.add("EngineVoice")
```
`:state()` returns `{ phase, voice, rpm, gear, runs }`.
# EngineVoicesShowcase
A listening stage for the `engineVoices` package. A player stands on a floor in front of a red plinth and a red lane. Once the player's camera is up, the entrypoint adds an `AudioListener` to it if there is none and calls `engineVoices.showcase.start()`. A stand-in car on the plinth then runs through all eight voices in turn: idle, a full-throttle pull through the gears, the rev limiter, a lift with pops and blow-off, a slide across tarmac, gravel and grass, and a crash. A second stand-in drives along the lane at 38 m/s, so the Doppler drop is heard as it passes. Use it to audition the voices or to check a change to them.
# ev_blowoff
A turbo blow-off valve: a sharp pressurised hiss as the throttle lifts at boost. engineVoices plays it on the turbo and diesel voices when the throttle closes with boost up.
# ev_crash_heavy
A heavy car crash: metal crumpling, glass and plastic breaking. engineVoices plays it, spatially, when a car hits something at high speed (car.impactSpeed above ~14 m/s).
# ev_crash_light
A light car knock: a dull bump and a short scrape of bodywork. engineVoices plays it on low-speed impacts (car.impactSpeed under ~7 m/s).
# ev_crash_medium
A medium car crash: a solid metallic thump with crunching panels. engineVoices plays it on mid-speed impacts.
# ev_diesel_1_off
Seamless one-second loop of the `diesel` engine voice (Turbo diesel) at 840 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 840 and crossfades it with the other bands and load takes.
# ev_diesel_1_on
Seamless one-second loop of the `diesel` engine voice (Turbo diesel) at 840 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 840 and crossfades it with the other bands and load takes.
# ev_diesel_2_off
Seamless one-second loop of the `diesel` engine voice (Turbo diesel) at 2160 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 2160 and crossfades it with the other bands and load takes.
# ev_diesel_2_on
Seamless one-second loop of the `diesel` engine voice (Turbo diesel) at 2160 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 2160 and crossfades it with the other bands and load takes.
# ev_diesel_3_off
Seamless one-second loop of the `diesel` engine voice (Turbo diesel) at 3720 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 3720 and crossfades it with the other bands and load takes.
# ev_diesel_3_on
Seamless one-second loop of the `diesel` engine voice (Turbo diesel) at 3720 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 3720 and crossfades it with the other bands and load takes.
# ev_electric_1_off
Seamless one-second loop of the `electric` engine voice (Electric motor) at 1200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1200 and crossfades it with the other bands and load takes.
# ev_electric_1_on
Seamless one-second loop of the `electric` engine voice (Electric motor) at 1200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1200 and crossfades it with the other bands and load takes.
# ev_electric_2_off
Seamless one-second loop of the `electric` engine voice (Electric motor) at 4800 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 4800 and crossfades it with the other bands and load takes.
# ev_electric_2_on
Seamless one-second loop of the `electric` engine voice (Electric motor) at 4800 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 4800 and crossfades it with the other bands and load takes.
# ev_electric_3_off
Seamless one-second loop of the `electric` engine voice (Electric motor) at 12000 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 12000 and crossfades it with the other bands and load takes.
# ev_electric_3_on
Seamless one-second loop of the `electric` engine voice (Electric motor) at 12000 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 12000 and crossfades it with the other bands and load takes.
# ev_four_1_off
Seamless one-second loop of the `four` engine voice (Four-cylinder) at 1080 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1080 and crossfades it with the other bands and load takes.
# ev_four_1_on
Seamless one-second loop of the `four` engine voice (Four-cylinder) at 1080 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1080 and crossfades it with the other bands and load takes.
# ev_four_2_off
Seamless one-second loop of the `four` engine voice (Four-cylinder) at 3000 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 3000 and crossfades it with the other bands and load takes.
# ev_four_2_on
Seamless one-second loop of the `four` engine voice (Four-cylinder) at 3000 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 3000 and crossfades it with the other bands and load takes.
# ev_four_3_off
Seamless one-second loop of the `four` engine voice (Four-cylinder) at 6000 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 6000 and crossfades it with the other bands and load takes.
# ev_four_3_on
Seamless one-second loop of the `four` engine voice (Four-cylinder) at 6000 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 6000 and crossfades it with the other bands and load takes.
# ev_inverter
Seamless one-second electric-motor inverter whine loop (1.8 kHz switching tone with sidebands and hiss). Played under the `electric` voice, louder on load.
# ev_kart_1_off
Seamless one-second loop of the `kart` engine voice (Two-stroke kart) at 2400 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 2400 and crossfades it with the other bands and load takes.
# ev_kart_1_on
Seamless one-second loop of the `kart` engine voice (Two-stroke kart) at 2400 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 2400 and crossfades it with the other bands and load takes.
# ev_kart_2_off
Seamless one-second loop of the `kart` engine voice (Two-stroke kart) at 7200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 7200 and crossfades it with the other bands and load takes.
# ev_kart_2_on
Seamless one-second loop of the `kart` engine voice (Two-stroke kart) at 7200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 7200 and crossfades it with the other bands and load takes.
# ev_kart_3_off
Seamless one-second loop of the `kart` engine voice (Two-stroke kart) at 13200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 13200 and crossfades it with the other bands and load takes.
# ev_kart_3_on
Seamless one-second loop of the `kart` engine voice (Two-stroke kart) at 13200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 13200 and crossfades it with the other bands and load takes.
# ev_pop_1
Exhaust overrun pop/crackle one-shot, take 1 of 3. `EngineVoice` fires these at random when a v8, rotary or turbo voice lifts off the throttle at high rpm, and on the rev limiter.
# ev_pop_2
Exhaust overrun pop/crackle one-shot, take 2 of 3. `EngineVoice` fires these at random when a v8, rotary or turbo voice lifts off the throttle at high rpm, and on the rev limiter.
# ev_pop_3
Exhaust overrun pop/crackle one-shot, take 3 of 3. `EngineVoice` fires these at random when a v8, rotary or turbo voice lifts off the throttle at high rpm, and on the rev limiter.
# ev_rotary_1_off
Seamless one-second loop of the `rotary` engine voice (Twin-rotor rotary) at 1200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1200 and crossfades it with the other bands and load takes.
# ev_rotary_1_on
Seamless one-second loop of the `rotary` engine voice (Twin-rotor rotary) at 1200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1200 and crossfades it with the other bands and load takes.
# ev_rotary_2_off
Seamless one-second loop of the `rotary` engine voice (Twin-rotor rotary) at 4200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 4200 and crossfades it with the other bands and load takes.
# ev_rotary_2_on
Seamless one-second loop of the `rotary` engine voice (Twin-rotor rotary) at 4200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 4200 and crossfades it with the other bands and load takes.
# ev_rotary_3_off
Seamless one-second loop of the `rotary` engine voice (Twin-rotor rotary) at 8400 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 8400 and crossfades it with the other bands and load takes.
# ev_rotary_3_on
Seamless one-second loop of the `rotary` engine voice (Twin-rotor rotary) at 8400 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 8400 and crossfades it with the other bands and load takes.
# ev_shift_auto
Gear change one-shot for a torque-converter automatic: a soft driveline nudge.
# ev_shift_manual
Gear change one-shot for an H-pattern manual gearbox: a two-stage metallic clunk of synchro and dog teeth.
# ev_shift_seq
Gear change one-shot for a sequential dog box: the ignition-cut bang in the exhaust and a hard dog-ring clack.
# ev_skid_gravel
Seamless loop of tyres sliding on loose gravel: crunching stones and scrub. Cut from the generated `ev_skid_gravel_src` take with an equal-power crossfaded wrap. Used on gravel and dirt.
# ev_skid_gravel_src
Source recording of tyres sliding and scrabbling on gravel and dirt; engineVoices cuts its looping gravel skid layer from it, faded in by car.skid on loose surfaces.
# ev_skid_soft
Seamless loop of tyres sliding on soft ground (grass, mud, sand, snow): a muffled scrub. Cut from the generated `ev_skid_soft_src` take with an equal-power crossfaded wrap.
# ev_skid_soft_src
Source recording of tyres skidding on soft ground (grass, mud, sand, snow); engineVoices cuts its looping soft-surface skid layer from it.
# ev_skid_tarmac
Seamless tyre squeal loop for paved surfaces (tarmac, concrete, cobbles, rumble strips, oil), from soundstage's generated `vehicles.skid` take 3. `EngineVoice` follows `car.skid` with its volume.
# ev_turbo_1_off
Seamless one-second loop of the `turbo` engine voice (Turbo four) at 1080 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1080 and crossfades it with the other bands and load takes.
# ev_turbo_1_on
Seamless one-second loop of the `turbo` engine voice (Turbo four) at 1080 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1080 and crossfades it with the other bands and load takes.
# ev_turbo_2_off
Seamless one-second loop of the `turbo` engine voice (Turbo four) at 3000 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 3000 and crossfades it with the other bands and load takes.
# ev_turbo_2_on
Seamless one-second loop of the `turbo` engine voice (Turbo four) at 3000 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 3000 and crossfades it with the other bands and load takes.
# ev_turbo_3_off
Seamless one-second loop of the `turbo` engine voice (Turbo four) at 6000 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 6000 and crossfades it with the other bands and load takes.
# ev_turbo_3_on
Seamless one-second loop of the `turbo` engine voice (Turbo four) at 6000 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 6000 and crossfades it with the other bands and load takes.
# ev_v12_1_off
Seamless one-second loop of the `v12` engine voice (V12) at 1200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1200 and crossfades it with the other bands and load takes.
# ev_v12_1_on
Seamless one-second loop of the `v12` engine voice (V12) at 1200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 1200 and crossfades it with the other bands and load takes.
# ev_v12_2_off
Seamless one-second loop of the `v12` engine voice (V12) at 4200 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 4200 and crossfades it with the other bands and load takes.
# ev_v12_2_on
Seamless one-second loop of the `v12` engine voice (V12) at 4200 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 4200 and crossfades it with the other bands and load takes.
# ev_v12_3_off
Seamless one-second loop of the `v12` engine voice (V12) at 8400 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 8400 and crossfades it with the other bands and load takes.
# ev_v12_3_on
Seamless one-second loop of the `v12` engine voice (V12) at 8400 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 8400 and crossfades it with the other bands and load takes.
# ev_v8_1_off
Seamless one-second loop of the `v8` engine voice (Cross-plane V8) at 840 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 840 and crossfades it with the other bands and load takes.
# ev_v8_1_on
Seamless one-second loop of the `v8` engine voice (Cross-plane V8) at 840 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 840 and crossfades it with the other bands and load takes.
# ev_v8_2_off
Seamless one-second loop of the `v8` engine voice (Cross-plane V8) at 2520 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 2520 and crossfades it with the other bands and load takes.
# ev_v8_2_on
Seamless one-second loop of the `v8` engine voice (Cross-plane V8) at 2520 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 2520 and crossfades it with the other bands and load takes.
# ev_v8_3_off
Seamless one-second loop of the `v8` engine voice (Cross-plane V8) at 5040 rpm, off load (coasting / overrun, quieter). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 5040 and crossfades it with the other bands and load takes.
# ev_v8_3_on
Seamless one-second loop of the `v8` engine voice (Cross-plane V8) at 5040 rpm, on load (full throttle, harsh, more low harmonics). Baked by `engineVoices.synth`; `EngineVoice` pitches it by rpm / 5040 and crossfades it with the other bands and load takes.
# ev_whistle
Seamless one-second turbocharger spool whistle loop (2.4 kHz turbine tone plus air). `EngineVoice` pitches it by `car.boost` and fades it in with boost and load, for the `turbo` and `diesel` voices.
# engineVoices.showcase
Lets you hear all eight engine voices with no car in the world. `start()` places a stand-in car (`EngineVoiceDemo` + `EngineVoice`) 12 m in front of the listener. It sweeps idle, the gears, the rev limiter, overrun, a slide across three surfaces and a crash, then steps on to the next voice. A second stand-in drives past at 38 m/s for the Doppler drop. If the scene has no `AudioListener`, one is added to the active camera. `stop()` removes them. The `EngineVoicesShowcase` scene in this package calls it on load.
```lua
local Showcase = require("engineVoices.showcase")
Showcase.start() -- every voice, plus a fly-by
Showcase.start({ voice = "rotary", flyby = false })
Showcase.stop()
```
# engineVoices.synth
The deterministic generator behind the engine voices, and the analysis that checks them. Re-run it after you change a voice's parameters in `engineVoices.voices`. Nothing at runtime requires it.
Each loop is one second of 32 kHz mono PCM. A loop holds a whole number of engine cycles: every band's reference rpm gives a cycle frequency that is a whole number of Hz (rpm/120 for four-strokes, rpm/60 for two-strokes and the rotary). Firing pulses (gamma-shaped pressure pulses plus combustion-noise bursts) are placed modulo the loop length. Every filter (two-pole low-pass, RBJ band-pass pipe resonances, a DC-blocking high-pass) runs over the buffer twice, so its state wraps, and the result loops with no seam. Per-cylinder imbalance, per-firing amplitude and timing jitter, misfires (kart four-stroking, rotary brap), per-bank pipes and delays (V8 cross-plane burble, V12 banks), diesel knock and valvetrain ticks shape the character. A tanh drive stage gives the on-load takes their extra harmonics. Loudness is set per take: on-load at 0.2 RMS and off-load at about 0.085.
```lua
local Synth = require("engineVoices.synth")
Synth.bakeAll() -- 48 loops + shared clips, about 6 s
Synth.bakeVoice("v8") -- one voice's six loops
Synth.bakeShared() -- whistle, inverter, shifts, pops, skid loops
local x, ref = Synth.render("kart", 1, true) -- samples without baking
Synth.analyze("ev_v8_2_on") --> { peakHz = 168, firingHz = 168, peakOverFiring = 1, harmonicShare = .., rms = .. }
```
`bakeShared` builds the tarmac squeal from soundstage's generated `ss_vehicles_skid_3` take when soundstage is installed. It builds the gravel and soft loops by cutting the steady middle of the generated `ev_skid_*_src` takes and crossfading the wrap.
# engineVoices
Checks the voice table, voice selection and the synthesis, without playing audio:
- **Voices**: the eight voices in `car.voiceId` order, each with its own id, a label and sane idle / redline / limiter values. An unknown voice raises.
- **Selection from car values**: `nameOf` / `idOf` are inverse, out-of-range ids fall back to the four, and every torqueCars engine's `voiceId` selects the voice its catalog entry names (skipped without torqueCars).
- **EngineVoice helpers**: `attach` adds the component (`auto` by default), attaching again updates the same component, `set` switches the voice, an unknown voice raises and `detach` removes the component.
- **Following rpm**: `firingHz` per engine type (four-stroke, two-stroke kart, rotary, electric motor order) is linear in rpm. Every voice's three band references ascend and give a whole number of cycles per second (seamless loops). The baked `ev_four_2_on` loop peaks exactly on its firing frequency.
- **Following load**: every on-load take is louder than its off-load take, in the specs and in rendered samples (v8 band 2). Rendering is deterministic.
- **Tables**: every clip the runtime plays resolves (48 loops, aux, shifts, pops, crashes, blow-off, skids), `loopName` spells the clip names, and the skid sound per surface follows driveSurfaces' order.
It leaves alone the per-frame mix of a live `EngineVoice` (band weights, smoothing, limiter stutter, pops, Doppler) and actual playback, which run only in play with an audio listener. It also does not re-bake any clips. Stand-in cars are spawned far below the play area and despawned after each test.
Run it in edit mode:
```luau
return asset.resolve("engineVoices", "testSuite"):run()
```
# engineVoices.voices
The eight engine voices, in the order `car.voiceId` uses: `four`, `v8`, `v12`, `diesel`, `rotary`, `turbo`, `electric`, `kart`. The module holds each voice's description and its default idle, redline and limiter rpm, plus the synthesis parameters that `engineVoices.synth` bakes it from. It also holds every clip the runtime plays (static identities, so a publish pins them), the skid sound for each `driveSurfaces` surface, and the calls that put a voice on a car. `EngineVoice` is the component that calls it at runtime. Game code calls `attach`, `set` and `names`.
```lua
local Voices = require("engineVoices.voices")
Voices.names() --> { "four", "v8", "v12", "diesel", "rotary", "turbo", "electric", "kart" }
Voices.attach(car) -- EngineVoice on the chassis, voice from car.voiceId
Voices.attach(car, "v12") -- forced voice (or Voices.set(car, "v12") later)
Voices.detach(car)
Voices.get("v8").blurb -- "Cross-plane V8 rumble and burble: ..."
Voices.surface(5).skid --> "gravel"
Voices.firingHz("v8", 3000) --> 200
```
The calls are `names()`, `has(name)`, `get(name)`, `nameOf(id)`, `idOf(name)`, `loopName(voice, band, onLoad)`, `firingHz(voice, rpm)`, `clips()`, `surface(id)`, `attach(car, voice?, volume?)`, `set(car, voice)` and `detach(car)`.