SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 1ba42f1ca1d6…
- Author-recorded commit
- baecad10b1cd…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Procedural stand-ins for every sound the game plays, rendered sample by
// sample into AudioBuffers at load time. Used when the bundle has no sound
// files (the custom bundle). Names match the original cast members, and the
// engine samples keep their roles: CarStart revs up into CarLoop's pitch,
// CarLoop loops seamlessly (whole cycles only), CarEnd winds down.
const RATE = 22050;
/** Deterministic noise (mulberry32). */
function noise(seed: number): () => number {
let a = seed >>> 0;
return () => {
a = (a + 0x6d2b79f5) >>> 0;
let t = a;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return (((t ^ (t >>> 14)) >>> 0) / 4294967296) * 2 - 1;
};
}
type Render = (out: Float32Array) => void;
const TAU = Math.PI * 2;
const tri = (p: number) => 1 - 4 * Math.abs((p % 1) - 0.5);
const square = (p: number, duty = 0.5) => ((p % 1) < duty ? 1 : -1);
const saw = (p: number) => 2 * (p % 1) - 1;
const midi = (n: number) => 440 * Math.pow(2, (n - 69) / 12);
/** One-pole low-pass, in place. */
function lowpass(x: Float32Array, cutoff: number): void {
const k = 1 - Math.exp((-TAU * cutoff) / RATE);
let y = 0;
for (let i = 0; i < x.length; i++) x[i] = y += k * (x[i] - y);
}
// --- engine ----------------------------------------------------------------------------
const IDLE_HZ = 70;
const LOOP_HZ = 110;
/** An electric RC motor: a buzzy whine with a gear-mesh overtone. Phase is integrated. */
function motor(freq: (t: number) => number, gain: (t: number) => number): Render {
return (out) => {
let phase = 0;
for (let i = 0; i < out.length; i++) {
const t = i / RATE;
phase += freq(t) / RATE;
const v = 0.5 * saw(phase) + 0.3 * square(phase * 2, 0.3) + 0.25 * Math.sin(TAU * phase * 6.5);
out[i] = v * gain(t) * 0.5;
}
lowpass(out, 2600);
};
}
const carStart: [number, Render] = [2.0, motor((t) => IDLE_HZ + (LOOP_HZ - IDLE_HZ) * Math.min(1, t / 1.8) ** 0.7, (t) => Math.min(1, t * 8))];
// 1.0 s × 110 Hz: whole cycles, so the loop point is seamless (the overtone at
// 6.5× completes 715 cycles too). The low-pass settles before the end.
const carLoop: [number, Render] = [
1.0,
(out) => {
const one = new Float32Array(out.length * 2);
motor(() => LOOP_HZ, () => 1)(one);
out.set(one.subarray(out.length));
},
];
const carEnd: [number, Render] = [1.0, motor((t) => LOOP_HZ - (LOOP_HZ - 40) * Math.min(1, t) ** 0.6, (t) => Math.max(0, 1 - t) ** 1.5)];
// --- one-shots ---------------------------------------------------------------------------
/** Servo/tyre chirp when the steering flicks. */
function turn(seed: number, up: boolean): [number, Render] {
const n = noise(seed);
return [
0.12,
(out) => {
let phase = 0;
for (let i = 0; i < out.length; i++) {
const t = i / out.length;
phase += (up ? 900 + 500 * t : 1400 - 500 * t) / RATE;
out[i] = (0.5 * tri(phase) + 0.15 * n()) * Math.sin(Math.PI * t) * 0.6;
}
},
];
}
/** Plastic-on-wood knock: a pitched thump and a burst of filtered noise. */
function crash(seed: number, glass: boolean): [number, Render] {
const n = noise(seed);
const pitch = 80 + ((seed * 37) % 60);
const length = glass ? 0.45 : 0.3 + ((seed * 13) % 10) / 50;
return [
length,
(out) => {
const body = new Float32Array(out.length);
for (let i = 0; i < out.length; i++) body[i] = n();
lowpass(body, glass ? 5000 : 1400);
let phase = 0;
for (let i = 0; i < out.length; i++) {
const t = i / RATE;
phase += pitch * (1 - t) / RATE;
let v = Math.sin(TAU * phase) * Math.exp(-t * 18) + body[i] * Math.exp(-t * (glass ? 9 : 14)) * 1.4;
// Plastic rattles: a few bright partials ringing out.
if (glass) v += 0.25 * (Math.sin(TAU * 2310 * t) + Math.sin(TAU * 3170 * t)) * Math.exp(-t * 12);
out[i] = v * 0.7;
}
},
];
}
interface Note {
/** MIDI note, or null for a rest. */
n: number | null;
/** Length in beats. */
len: number;
}
/** A short melodic jingle on a square lead with a triangle under it. */
function jingle(notes: Note[], bpm: number, tail = 0.25): [number, Render] {
const beat = 60 / bpm;
const total = notes.reduce((s, x) => s + x.len, 0) * beat + tail;
return [
total,
(out) => {
let at = 0;
for (const { n, len } of notes) {
const start = Math.floor(at * RATE);
const dur = len * beat;
at += dur;
if (n === null) continue;
const f = midi(n);
const end = Math.min(out.length, start + Math.floor((dur + tail) * RATE));
for (let i = start; i < end; i++) {
const t = (i - start) / RATE;
const env = Math.min(1, t * 200) * Math.exp(-t * 3.5) * (t < dur ? 1 : Math.max(0, 1 - (t - dur) / tail));
out[i] += (0.32 * square(f * t, 0.25) + 0.3 * tri(f * 0.5 * t)) * env;
}
}
},
];
}
const N = (n: number | null, len = 0.5): Note => ({ n, len });
function alarm(): [number, Render] {
return [
0.6,
(out) => {
for (let i = 0; i < out.length; i++) {
const t = i / RATE;
const on = t % 0.2 < 0.12 ? 1 : 0;
out[i] = 0.35 * square(t * (t % 0.4 < 0.2 ? 1320 : 990)) * on;
}
lowpass(out, 4000);
},
];
}
function click(): [number, Render] {
return [
0.04,
(out) => {
for (let i = 0; i < out.length; i++) {
const t = i / RATE;
out[i] = 0.6 * Math.sin(TAU * 1800 * t) * Math.exp(-t * 160);
}
},
];
}
// --- music -------------------------------------------------------------------------------
const BPM = 132;
const STEP = 60 / BPM / 4; // sixteenth notes
/** Chord roots for each bar, all diatonic to C major. */
const PROGRESSIONS = [
[57, 53, 48, 55], // Am F C G
[48, 55, 57, 53], // C G Am F
[53, 55, 48, 48], // F G C C
[57, 55, 53, 52], // Am G F E
[50, 53, 48, 55], // Dm F C G
[48, 53, 55, 55], // C F G G (splash)
];
const SCALE = [0, 2, 4, 5, 7, 9, 11];
/**
* Four bars of chiptune: kick/snare/hat, a pumping bass on the chord root and
* a seeded lead that sticks to chord tones on strong beats. Every loop shares
* tempo and key, so back-to-back loops in any order stay in time.
*/
function musicLoop(index: number): [number, Render] {
const bars = 4;
const steps = bars * 16;
const length = steps * STEP;
return [
length,
(out) => {
const rnd = noise(1000 + index * 77);
const hat = noise(5 + index);
const roots = PROGRESSIONS[index];
// Lead line: one note per eighth, some rests.
const lead: Array<number | null> = [];
let degree = 4;
for (let s = 0; s < steps / 2; s++) {
const strong = s % 4 === 0;
if (!strong && rnd() < 0.25) {
lead.push(null);
continue;
}
degree += Math.round((rnd() * 2 - 1) * 2);
degree = Math.max(0, Math.min(9, degree));
if (strong) degree -= degree % 2; // land on chord-ish tones
// Diatonic to C major from C5: fits every chord in the progressions.
lead.push(72 + SCALE[degree % 7] + Math.floor(degree / 7) * 12);
}
for (let i = 0; i < out.length; i++) {
const t = i / RATE;
const step = Math.floor(t / STEP);
const st = t - step * STEP;
const inBar = step % 16;
const root = roots[Math.floor(step / 16) % bars];
let v = 0;
// Kick on the beat.
if (inBar % 4 === 0) {
const ft = 50 + 120 * Math.exp(-st * 40);
v += 0.55 * Math.sin(TAU * ft * st) * Math.exp(-st * 14);
}
// Snare on 2 and 4.
if (inBar === 4 || inBar === 12) v += 0.28 * hat() * Math.exp(-st * 22);
// Hats on off-eighths.
if (inBar % 2 === 1) v += 0.07 * hat() * Math.exp(-st * 70);
// Bass: eighth-note pulse on the root, octave jump on the "and".
const bassNote = root - 12 + (inBar % 4 === 2 ? 12 : 0);
const bt = t % (STEP * 2);
v += 0.22 * tri(midi(bassNote) * t) * Math.exp(-bt * 6);
// Lead.
const ln = lead[Math.floor(step / 2)];
if (ln !== null && ln !== undefined) {
const lt = t % (STEP * 2);
v += 0.12 * square(midi(ln) * t, 0.25) * Math.min(1, lt * 300) * Math.exp(-lt * 5);
}
out[i] = v * 0.8;
}
},
];
}
const RECIPES: Record<string, () => [number, Render]> = {
carstart: () => carStart,
carloop: () => carLoop,
carend: () => carEnd,
carturnleft01: () => turn(1, true),
carturnleft02: () => turn(2, true),
carturnleft03: () => turn(3, true),
carturnright01: () => turn(4, false),
carturnright02: () => turn(5, false),
carturnright03: () => turn(6, false),
...Object.fromEntries([1, 2, 3, 4, 5, 6, 7, 8].map((i) => [`crash${i}`, () => crash(i, false)])),
crash1_glass: () => crash(11, true),
crash2_glass: () => crash(12, true),
crash3_glass: () => crash(13, true),
alarm,
buttonclick: click,
stingcheckpoint: () => jingle([N(72, 0.25), N(76, 0.25), N(79, 0.25), N(84, 0.75)], 160),
stinghiddentreasure: () => jingle([N(84, 0.25), N(88, 0.25), N(91, 0.25), N(96, 0.25), N(91, 0.25), N(96, 0.75)], 200),
stingwin: () => jingle([N(67), N(72), N(76), N(79, 1), N(76), N(79, 2)], 140, 0.5),
stingloseloop: () => jingle([N(72), N(71), N(70), N(69, 2)], 110, 0.5),
ssrmusicsplashscreen: () => musicLoop(5),
musicloop1: () => musicLoop(0),
musicloop2: () => musicLoop(1),
musicloop3: () => musicLoop(2),
musicloop4: () => musicLoop(3),
musicloop5: () => musicLoop(4),
};
/** Render the stand-in for a sound name (case-insensitive), or undefined if there is none. */
export function synthesize(context: BaseAudioContext, name: string): AudioBuffer | undefined {
const recipe = RECIPES[name.toLowerCase()];
if (!recipe) return undefined;
const [seconds, render] = recipe();
const buffer = context.createBuffer(1, Math.max(1, Math.round(seconds * RATE)), RATE);
const data = new Float32Array(buffer.length);
render(data);
for (let i = 0; i < data.length; i++) data[i] = Math.max(-1, Math.min(1, data[i]));
buffer.copyToChannel(data, 0);
return buffer;
}
