feat: couple terrain surge and sun bloom to the soundtrack's low end
This commit is contained in:
41
src/audio/music-pulse.ts
Normal file
41
src/audio/music-pulse.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* A single low-band level shared between the soundtrack and the scene.
|
||||
* The scene samples it once inside its existing render loop; nothing here
|
||||
* schedules work of its own. When no soundtrack is audible it reads zero.
|
||||
*/
|
||||
let analyser: AnalyserNode | null = null;
|
||||
let bins: Uint8Array<ArrayBuffer> | null = null;
|
||||
let audible = false;
|
||||
|
||||
export function attachMusicPulse(node: AnalyserNode | null): void {
|
||||
analyser = node;
|
||||
bins = node === null ? null : new Uint8Array(new ArrayBuffer(node.frequencyBinCount));
|
||||
}
|
||||
|
||||
export function setMusicPulseAudible(value: boolean): void {
|
||||
audible = value;
|
||||
}
|
||||
|
||||
/** Bass energy in [0, 1]: the mean of roughly 90-380 Hz. */
|
||||
export function readMusicPulse(): number {
|
||||
if (!audible || analyser === null || bins === null) return 0;
|
||||
analyser.getByteFrequencyData(bins);
|
||||
const sum = (bins[1] ?? 0) + (bins[2] ?? 0) + (bins[3] ?? 0) + (bins[4] ?? 0);
|
||||
return sum / (4 * 255);
|
||||
}
|
||||
|
||||
/** Insert a pass-through tap in front of `destination` and analyse it. */
|
||||
export function createPulseTap(context: BaseAudioContext, destination: AudioNode): AudioNode {
|
||||
const tap = context.createGain();
|
||||
tap.connect(destination);
|
||||
try {
|
||||
const node = context.createAnalyser();
|
||||
node.fftSize = 512;
|
||||
node.smoothingTimeConstant = 0.55;
|
||||
tap.connect(node);
|
||||
attachMusicPulse(node);
|
||||
} catch {
|
||||
attachMusicPulse(null);
|
||||
}
|
||||
return tap;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { attachMusicPulse, createPulseTap } from './music-pulse';
|
||||
import {
|
||||
AFTERGLOW_LOOP_END_SECONDS,
|
||||
AFTERGLOW_LOOP_START_SECONDS,
|
||||
@@ -596,12 +597,17 @@ export function createBrowserSoundtrackEngine(
|
||||
sampleRate: SOUNDTRACK_SAMPLE_RATE,
|
||||
}),
|
||||
loadBuffer: loadAudioBuffer,
|
||||
createMixer: createAdaptiveMixer,
|
||||
// The mixer feeds a pass-through tap so the scene can read the bass level.
|
||||
createMixer: (context, destination) => createAdaptiveMixer(context, createPulseTap(context, destination)),
|
||||
createDriveTexture,
|
||||
});
|
||||
let resumeCount = 0;
|
||||
return {
|
||||
...graph,
|
||||
async close() {
|
||||
attachMusicPulse(null);
|
||||
await graph.close();
|
||||
},
|
||||
async resume() {
|
||||
resumeCount += 1;
|
||||
if (isTestFault('F11-audio-resume-rejection') && resumeCount > 1) {
|
||||
|
||||
Reference in New Issue
Block a user