feat(audio): sweep a pass-by through the stereo field on each exit
This commit is contained in:
@@ -8,9 +8,13 @@ const CRUISE_ENGINE_GAIN = 0.055;
|
||||
const MAX_ENGINE_GAIN = 0.12;
|
||||
const CRUISE_ROAD_GAIN = 0.006;
|
||||
const MAX_ROAD_GAIN = 0.024;
|
||||
const PASS_BY_GAIN = 0.05;
|
||||
const PASS_BY_SECONDS = 1.6;
|
||||
|
||||
export interface DriveTexture {
|
||||
setEnergy(sample: DriveEnergySample): void;
|
||||
/** The exit gates going by: a filtered air sweep across the stereo field. */
|
||||
passBy?(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -95,11 +99,29 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
||||
roadLowpass.connect(roadGain);
|
||||
roadGain.connect(destination);
|
||||
|
||||
// Pass-by: preallocated and silent until an exit, so a chapter change
|
||||
// schedules automation only and never creates nodes.
|
||||
const passSource = context.createBufferSource();
|
||||
passSource.buffer = roadSource.buffer;
|
||||
passSource.loop = true;
|
||||
const passBand = context.createBiquadFilter();
|
||||
passBand.type = 'bandpass';
|
||||
passBand.frequency.value = 500;
|
||||
passBand.Q.value = 0.9;
|
||||
const passPan = context.createStereoPanner();
|
||||
const passGain = context.createGain();
|
||||
passGain.gain.value = 0;
|
||||
passSource.connect(passBand);
|
||||
passBand.connect(passPan);
|
||||
passPan.connect(passGain);
|
||||
passGain.connect(destination);
|
||||
|
||||
const sources: readonly AudioScheduledSourceNode[] = [
|
||||
fundamental.source,
|
||||
harmonic.source,
|
||||
ignition.source,
|
||||
roadSource,
|
||||
passSource,
|
||||
];
|
||||
const ownedNodes: readonly AudioNode[] = [
|
||||
...sources,
|
||||
@@ -111,6 +133,9 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
||||
roadHighpass,
|
||||
roadLowpass,
|
||||
roadGain,
|
||||
passBand,
|
||||
passPan,
|
||||
passGain,
|
||||
];
|
||||
for (const source of sources) source.start();
|
||||
|
||||
@@ -135,6 +160,19 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
||||
automate(roadGain.gain, CRUISE_ROAD_GAIN + next * (MAX_ROAD_GAIN - CRUISE_ROAD_GAIN), duration);
|
||||
currentEnergy = next;
|
||||
},
|
||||
passBy() {
|
||||
if (disposed) return;
|
||||
const now = context.currentTime;
|
||||
for (const parameter of [passGain.gain, passBand.frequency, passPan.pan]) {
|
||||
holdParameter(parameter, now);
|
||||
}
|
||||
passGain.gain.linearRampToValueAtTime(PASS_BY_GAIN, now + 0.5);
|
||||
passGain.gain.linearRampToValueAtTime(0, now + PASS_BY_SECONDS);
|
||||
passBand.frequency.linearRampToValueAtTime(2_600, now + 0.5);
|
||||
passBand.frequency.linearRampToValueAtTime(380, now + PASS_BY_SECONDS);
|
||||
passPan.pan.linearRampToValueAtTime(-0.75, now + 0.05);
|
||||
passPan.pan.linearRampToValueAtTime(0.75, now + PASS_BY_SECONDS);
|
||||
},
|
||||
dispose() {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
@@ -554,6 +554,9 @@ export function createSoundtrackEngine(
|
||||
},
|
||||
setChapterIntent(intent) {
|
||||
if (closed) return;
|
||||
// The scene runs its exit gates on every chapter change; the drive
|
||||
// texture answers with the air of the gates going by.
|
||||
if (intent.sectionId !== latestChapter.sectionId) driveTexture?.passBy?.();
|
||||
latestChapter = intent;
|
||||
mixer.setChapterIntent(intent);
|
||||
if (!mainCommitted && introStarted) {
|
||||
|
||||
Reference in New Issue
Block a user