fleeting-garden/src/audio/noise-burst-player.ts
2026-05-18 08:11:52 +01:00

61 lines
2.1 KiB
TypeScript

import { createAudioPanNode } from './audio-pan-node';
import type { GardenAudioGraph } from './garden-audio-graph';
import type { NoiseBurst } from './garden-audio-types';
const noiseBurstTuning = {
attackSeconds: 0.004,
filterQ: 1.4,
offsetRandomSeconds: 0.4,
scheduleAheadSeconds: 0.002,
silentGain: 0.0001,
filterType: 'bandpass' as BiquadFilterType,
};
export class NoiseBurstPlayer {
public constructor(private readonly graph: GardenAudioGraph) {}
public play({ startTime, durationSeconds, gain, filterHz, pan }: NoiseBurst): void {
const { context, eventBus, noiseBus, noiseBuffer } = this.graph;
const outputBus = noiseBus ?? eventBus;
if (!context || !outputBus || !noiseBuffer) {
return;
}
const scheduledStart = Math.max(
context.currentTime + noiseBurstTuning.scheduleAheadSeconds,
startTime
);
const source = context.createBufferSource();
const filter = context.createBiquadFilter();
const envelope = context.createGain();
const panNode = createAudioPanNode(context, pan, scheduledStart);
const stopAt = scheduledStart + durationSeconds;
source.buffer = noiseBuffer;
filter.type = noiseBurstTuning.filterType;
filter.frequency.setValueAtTime(filterHz, scheduledStart);
filter.Q.value = noiseBurstTuning.filterQ;
envelope.gain.setValueAtTime(noiseBurstTuning.silentGain, scheduledStart);
envelope.gain.exponentialRampToValueAtTime(
Math.max(noiseBurstTuning.silentGain, gain),
scheduledStart + noiseBurstTuning.attackSeconds
);
envelope.gain.exponentialRampToValueAtTime(noiseBurstTuning.silentGain, stopAt);
source.connect(filter);
filter.connect(envelope);
envelope.connect(panNode.input);
panNode.output.connect(outputBus);
source.start(scheduledStart, Math.random() * noiseBurstTuning.offsetRandomSeconds);
source.stop(stopAt);
source.addEventListener(
'ended',
() => {
source.disconnect();
filter.disconnect();
envelope.disconnect();
panNode.disconnect();
},
{ once: true }
);
}
}