This commit is contained in:
Andras Schmelczer 2026-05-16 15:05:35 +01:00
commit 1fe5015056
55 changed files with 2036 additions and 685 deletions

View file

@ -1,5 +1,6 @@
import type { GardenAudioEngineConfig } from '../config';
import { clamp, clamp01 } from '../utils/clamp';
import { ErrorHandler, Severity } from '../utils/error-handler';
import { VibePreset } from '../vibes';
import { GardenAudioConfig } from './garden-audio-config';
import { GardenAudioEnergy } from './garden-audio-energy';
@ -71,40 +72,66 @@ export class GardenAudio {
return;
}
const startupRampSeconds =
options.userGesture === true
? this.engineConfig.muteRampSeconds
: this.config.fadeInSeconds;
const needsResume = context.state !== 'running' && context.state !== 'closed';
let resumePromise: Promise<void> | null = null;
if (needsResume) {
if (options.userGesture !== true) {
return;
}
resumePromise = context.resume();
}
if (options.userGesture === true) {
this.graph.unlock();
}
if (context.state === 'suspended') {
if (options.userGesture !== true) {
return;
}
void context.resume().catch(() => undefined);
if (resumePromise) {
void resumePromise
.then(() => {
if (this.graph.context === context && !this.isDestroyed && !this.isMuted) {
this.graph.unlock();
this.graph.setMasterGain(this.config.masterVolume, startupRampSeconds);
}
})
.catch((error) => {
ErrorHandler.addException(error, {
fallbackMessage: 'Could not resume audio playback.',
severity: Severity.WARNING,
});
});
}
this.hasStarted = true;
this.applyVibe(vibe);
this.pianoEngine.prime(context.currentTime);
this.graph.setMasterGain(
this.config.masterVolume,
options.userGesture === true
? this.engineConfig.muteRampSeconds
: this.config.fadeInSeconds
);
this.graph.setMasterGain(this.config.masterVolume, startupRampSeconds);
if (!this.hasQueuedPianoLoad) {
this.hasQueuedPianoLoad = true;
void this.piano.load(context).then(() => {
if (this.graph.context === context && !this.isDestroyed) {
this.pianoEngine.cue(context.currentTime);
}
});
void this.piano
.load(context)
.then(() => {
if (this.graph.context === context && !this.isDestroyed) {
this.pianoEngine.cue(context.currentTime);
}
})
.catch(() => undefined);
}
}
public changeVibe(vibe: VibePreset, options: GardenAudioStartOptions = {}): void {
const previousVibeId = this.currentVibeId;
this.start(vibe, options);
const didChangeVibe = previousVibeId !== null && previousVibeId !== vibe.id;
if (didChangeVibe) {
this.piano.stopAll();
}
const context = this.graph.context;
if (
@ -112,8 +139,7 @@ export class GardenAudio {
(context.state === 'running' || options.userGesture === true) &&
!this.isMuted &&
!this.isDestroyed &&
previousVibeId !== null &&
previousVibeId !== vibe.id
didChangeVibe
) {
this.playVibeChangeStinger(vibe);
}
@ -158,7 +184,7 @@ export class GardenAudio {
this.selectedColorIndex = normalizeColorIndex(touch.colorIndex);
const mirrorAmount = this.getMirrorAmount(touch.mirrorSegmentCount ?? 1);
const pressure = this.getTouchPressure(touch.pressure, touch.pointerType);
const pressure = this.getPressure(touch.pressure);
const strength = clamp01(0.36 + pressure * 0.34 + mirrorAmount * 0.22);
const frame = this.gestureState.recordTouchDown({
touch,
@ -225,7 +251,6 @@ export class GardenAudio {
const metrics = getStrokeMetrics(
stroke,
this.config.rhythm.speedForFullEnergyPixelsPerSecond,
this.config.input.pressureFallback,
this.engineConfig.input
);
const now = context.currentTime;
@ -375,16 +400,11 @@ export class GardenAudio {
return clamp01((segmentCount - 1) / (maxMirrorSegmentCount - 1));
}
private getTouchPressure(pressure: number | undefined, pointerType?: string): number {
private getPressure(pressure: number | undefined): number {
if (pressure !== undefined && Number.isFinite(pressure) && pressure > 0) {
return clamp01(pressure);
}
return pointerType === 'pen'
? Math.max(
this.engineConfig.input.penMinPressure,
this.config.input.pressureFallback
)
: this.config.input.pressureFallback;
return 0;
}
}