Add generative garden audio

This commit is contained in:
Andras Schmelczer 2026-05-24 10:57:57 +01:00
commit c2efb33683
45 changed files with 3730 additions and 0 deletions

View file

@ -0,0 +1,64 @@
import { clamp } from '../utils/math';
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 const;
export class NoiseBurstPlayer {
public constructor(private readonly graph: GardenAudioGraph) {}
public play({ startTime, durationSeconds, gain, filterHz, pan }: NoiseBurst): void {
const { context, noiseBus, noiseBuffer } = this.graph;
if (!context || !noiseBus || !noiseBuffer) {
return;
}
const scheduledStart = Math.max(
context.currentTime + noiseBurstTuning.scheduleAheadSeconds,
startTime
);
const source = context.createBufferSource();
const filter = context.createBiquadFilter();
const envelope = context.createGain();
const panner = context.createStereoPanner();
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);
panner.pan.setValueAtTime(clamp(pan, -1, 1), scheduledStart);
source.connect(filter);
filter.connect(envelope);
envelope.connect(panner);
panner.connect(noiseBus);
const maxOffsetSeconds = Math.max(0, noiseBuffer.duration - durationSeconds);
const offsetSeconds =
Math.random() * Math.min(noiseBurstTuning.offsetRandomSeconds, maxOffsetSeconds);
source.start(scheduledStart, offsetSeconds);
source.stop(stopAt);
source.addEventListener(
'ended',
() => {
source.disconnect();
filter.disconnect();
envelope.disconnect();
panner.disconnect();
},
{ once: true }
);
}
}