feat: engine off at the destination
On arrival the drive winds down to a stop, the tail and running lamps drop to parking level, the sky darkens about 10% with more stars and the grid fades. The engine and road noise take a 1.2 s low-pass sweep to idle. The letterbox stays within 8vh and the closing credit types into the lower bar; reduced motion keeps only the bars and dimming.
This commit is contained in:
@@ -10,11 +10,15 @@ const CRUISE_ROAD_GAIN = 0.006;
|
||||
const MAX_ROAD_GAIN = 0.024;
|
||||
const PASS_BY_GAIN = 0.05;
|
||||
const PASS_BY_SECONDS = 1.6;
|
||||
/** Engine off at the destination: a low-pass sweep down to a parked idle. */
|
||||
const PARK_SECONDS = 1.2;
|
||||
|
||||
export interface DriveTexture {
|
||||
setEnergy(sample: DriveEnergySample): void;
|
||||
/** Passing under the gantry: a filtered air sweep across the stereo field. */
|
||||
passBy?(): void;
|
||||
/** Parked at the final exit (true) or driving again (false). */
|
||||
park?(parked: boolean): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -141,6 +145,7 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
||||
|
||||
let currentEnergy = 0;
|
||||
let disposed = false;
|
||||
let parked = false;
|
||||
const automate = (parameter: AudioParam, value: number, duration: number): void => {
|
||||
const now = context.currentTime;
|
||||
holdParameter(parameter, now);
|
||||
@@ -151,6 +156,10 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
||||
setEnergy(sample) {
|
||||
if (disposed) return;
|
||||
const next = Math.min(1, Math.max(0, sample.energy));
|
||||
if (parked) {
|
||||
currentEnergy = next;
|
||||
return;
|
||||
}
|
||||
const duration = next > currentEnergy ? ATTACK_SECONDS : RELEASE_SECONDS;
|
||||
automate(fundamental.source.frequency, 42 + next * 20, duration);
|
||||
automate(harmonic.source.frequency, 84 + next * 40, duration);
|
||||
@@ -160,6 +169,24 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
||||
automate(roadGain.gain, CRUISE_ROAD_GAIN + next * (MAX_ROAD_GAIN - CRUISE_ROAD_GAIN), duration);
|
||||
currentEnergy = next;
|
||||
},
|
||||
park(next) {
|
||||
if (disposed || next === parked) return;
|
||||
parked = next;
|
||||
if (!parked) {
|
||||
const energy = currentEnergy;
|
||||
currentEnergy = -1;
|
||||
this.setEnergy({ energy, source: 'idle' });
|
||||
return;
|
||||
}
|
||||
const now = context.currentTime;
|
||||
for (const parameter of [engineFilter.frequency, roadLowpass.frequency, engineGain.gain, roadGain.gain]) {
|
||||
holdParameter(parameter, now);
|
||||
}
|
||||
engineFilter.frequency.exponentialRampToValueAtTime(70, now + PARK_SECONDS);
|
||||
roadLowpass.frequency.exponentialRampToValueAtTime(260, now + PARK_SECONDS);
|
||||
engineGain.gain.linearRampToValueAtTime(CRUISE_ENGINE_GAIN * 0.3, now + PARK_SECONDS);
|
||||
roadGain.gain.linearRampToValueAtTime(CRUISE_ROAD_GAIN * 0.25, now + PARK_SECONDS);
|
||||
},
|
||||
passBy() {
|
||||
if (disposed) return;
|
||||
const now = context.currentTime;
|
||||
|
||||
Reference in New Issue
Block a user