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.
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.
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.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2engineVoices.synth — the deterministic generator behind every engine voice. Each voice is baked as seamless one-second loops at three rpm bands, one on-load and one off-load take per band. A loop holds a whole number of engine cycles (the band's reference rpm is chosen so the cycle frequency is a whole number of Hz), firing pulses are placed modulo the loop length and every filter runs over the buffer twice, so the last sample runs straight into the first: the loops have no seam at all. local Synth = require("engineVoices.synth") Synth.bakeAll() -- re-bake every clip of the package (about a minute) Synth.bakeVoice("v8") -- one voice's six loops Synth.analyze("ev_v8_2_on") -- spectral peak vs the expected firing frequency
rng(seed: number) → void
Park–Miller generator: the same seed always bakes the same samples.
| arg | type | description |
|---|---|---|
| seed | number |
zeros(n: number) → void
| arg | type | description |
|---|---|---|
| n | number |
addPulse(buf: { number }, n: number, pos: number, amp: number, width: number) → void
Gamma-shaped pressure pulse t*e^(1-t), peak 1 at t = width, written modulo n.
| arg | type | description |
|---|---|---|
| buf | { number } | |
| n | number | |
| pos | number | |
| amp | number | |
| width | number |
addBurst(buf: { number }, n: number, pos: number, amp: number, tau: number, r: () → void
Exponentially decaying white-noise burst, written modulo n.
| arg | type | description |
|---|---|---|
| buf | { number } | |
| n | number | |
| pos | number | |
| amp | number | |
| tau | number | |
| r | ( |
addBandpass(x: { number }, out: { number }, n: number, f: number, q: number, g: number) → void
RBJ band-pass (0 dB peak), run circularly: the first pass only settles the filter's state, the second writes, so the output wraps without a step.
| arg | type | description |
|---|---|---|
| x | { number } | |
| out | { number } | |
| n | number | |
| f | number | |
| q | number | |
| g | number |
addLowpass(x: { number }, out: { number }, n: number, f: number, g: number) → void
Two-pole low-pass (two cascaded one-poles), circular, added into out.
| arg | type | description |
|---|---|---|
| x | { number } | |
| out | { number } | |
| n | number | |
| f | number | |
| g | number |
highpass(x: { number }, n: number, f: number) → void
One-pole high-pass in place (removes the DC the unipolar pulses carry), circular.
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number | |
| f | number |
rms(x: { number }, n: number) → number
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number |
peak(x: { number }, n: number) → number
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number |
finish(x: { number }, n: number, drive: number, target: number) → void
Normalise to peak 1, push through a tanh-like soft clip (more drive = more harmonics, the on-load rasp), then set the loudness to `target` RMS.
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number | |
| drive | number | |
| target | number |
toBuffer(x: { number }, n: number) → buffer
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number |
take(spec: Spec, band: number, onLoad: boolean) → Take
| arg | type | description |
|---|---|---|
| spec | Spec | |
| band | number | |
| onLoad | boolean |
renderCombustion(spec: Spec, band: number, onLoad: boolean) → void
One combustion engine loop: `spec` from engineVoices.voices, band 1..3.
| arg | type | description |
|---|---|---|
| spec | Spec | |
| band | number | |
| onLoad | boolean |
renderElectric(spec: Spec, band: number, onLoad: boolean) → void
The electric motor: stator-order whine, gear mesh and a little hiss.
| arg | type | description |
|---|---|---|
| spec | Spec | |
| band | number | |
| onLoad | boolean |
bake(name: string, x: { number }, n: number, loop: boolean) → void
| arg | type | description |
|---|---|---|
| name | string | |
| x | { number } | |
| n | number | |
| loop | boolean |
render(voice: string, band: number, onLoad: boolean) →
Render one engine loop to a Luau array of samples (32 kHz mono, one second) without baking it.
| arg | type | description |
|---|---|---|
| voice | string | Voice name (`four`, `v8`, ...). |
| band | number | Rpm band 1..3. |
| onLoad | boolean | true for the on-load take, false for the off-load/overrun take. |
bakeVoice(voice: string) →
Bake one voice's six loops (three rpm bands x on/off load) into the package's clips folder.
| arg | type | description |
|---|---|---|
| voice | string | Voice name. |
renderWhistle( ) → void
renderInverter( ) → void
oneShot(len: number) → void
| arg | type | description |
|---|---|---|
| len | number |
tail(x: { number }, n: number, fadeMs: number) → void
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number | |
| fadeMs | number |
normalisePeak(x: { number }, n: number, p: number) → void
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number | |
| p | number |
renderPop(seed: number) → void
Exhaust pop: a pressure bang through the pipe modes plus crackle ticks.
| arg | type | description |
|---|---|---|
| seed | number |
renderShift(kind: string) → void
| arg | type | description |
|---|---|---|
| kind | string |
loopFrom(clipName: string, fromS: number, toS: number, fade: number) → void
Cut the steady middle of a generated take into a seamless loop: the last `fade` seconds are crossfaded (equal power) into the first.
| arg | type | description |
|---|---|---|
| clipName | string | |
| fromS | number | |
| toS | number | |
| fade | number |
mono(i: number) → number
| arg | type | description |
|---|---|---|
| i | number |
bakeAt(name: string, x: { number }, n: number, sr: number, desc: string, codec: string) → void
| arg | type | description |
|---|---|---|
| name | string | |
| x | { number } | |
| n | number | |
| sr | number | |
| desc | string | |
| codec | string |
bakeShared( ) →
Bake the shared clips: turbo whistle, inverter whine, shift sounds, exhaust pops and the three skid loops.
put(name: string, x: { number }, n: number, desc: string) → void
| arg | type | description |
|---|---|---|
| name | string | |
| x | { number } | |
| n | number | |
| desc | string |
bakeAll( ) →
Re-bake every clip the package generates: the 48 engine loops and the shared clips. Deterministic; run it from a task (it yields between clips).
goertzel(x: { number }, n: number, sr: number, f: number) → number
Goertzel magnitude of one frequency over a mono sample array.
| arg | type | description |
|---|---|---|
| x | { number } | |
| n | number | |
| sr | number | |
| f | number |
analyze(clipName: string, fromHz: number?, toHz: number?) →
Read a clip back and measure its spectrum: the strongest component between `fromHz` and `toHz` (1 Hz steps) and, for an engine loop, the energy at the firing frequency and its harmonics against the energy between them.
| arg | type | description |
|---|---|---|
| clipName | string | Clip identity, e.g. `ev_v8_2_on`. |
| fromHz | number? | Lowest frequency searched (default 20). |
| toHz | number? | Highest frequency searched (default 1500). |
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Showing up to 3 levels deep. Open a part to see what it holds further down.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.