This commit is contained in:
Andras Schmelczer 2026-05-17 17:21:49 +01:00
commit d6a8f898d1
27 changed files with 751 additions and 354 deletions

View file

@ -1,6 +1,6 @@
import { clamp01 } from '../utils/clamp';
import { ErrorHandler, Severity } from '../utils/error-handler';
import { VibePreset } from '../vibes';
import type { VibeId, VibePreset } from '../vibes';
import { GardenAudioConfig } from './garden-audio-config';
import { GardenAudioEnergy } from './garden-audio-energy';
import { GardenAudioGestureState } from './garden-audio-gesture-state';
@ -22,6 +22,8 @@ export type {
GardenAudioStroke,
} from './garden-audio-types';
type AudioLifecycle = 'idle' | 'started' | 'destroyed';
export class GardenAudio {
private readonly graph: GardenAudioGraph;
private readonly piano: PianoSampler;
@ -30,13 +32,11 @@ export class GardenAudio {
private readonly gestureState: GardenAudioGestureState;
private readonly pianoEngine: GenerativePianoEngine;
private currentVibeId: string | null = null;
private hasStarted = false;
private isDestroyed = false;
private currentVibeId: VibeId | null = null;
private lifecycle: AudioLifecycle = 'idle';
private isMuted = false;
private masterVolume: number;
private isGestureActive = false;
private hasQueuedPianoLoad = false;
private masterVolume: number;
private lastEraserAt = Number.NEGATIVE_INFINITY;
private lastVibeStingerAt = Number.NEGATIVE_INFINITY;
@ -51,7 +51,7 @@ export class GardenAudio {
}
public start(vibe: VibePreset, options: GardenAudioStartOptions = {}): void {
if (this.isDestroyed || this.isMuted) {
if (this.lifecycle === 'destroyed' || this.isMuted) {
return;
}
@ -81,7 +81,11 @@ export class GardenAudio {
if (resumePromise) {
void resumePromise
.then(() => {
if (this.graph.context === context && !this.isDestroyed && !this.isMuted) {
if (
this.graph.context === context &&
this.lifecycle !== 'destroyed' &&
!this.isMuted
) {
this.graph.unlock();
this.graph.setMasterGain(this.masterVolume, startupRampSeconds);
}
@ -94,17 +98,16 @@ export class GardenAudio {
});
}
this.hasStarted = true;
this.lifecycle = 'started';
this.applyVibe(vibe);
this.pianoEngine.prime(context.currentTime);
this.graph.setMasterGain(this.masterVolume, startupRampSeconds);
if (!this.hasQueuedPianoLoad) {
this.hasQueuedPianoLoad = true;
void this.piano
.load(context)
const pianoLoad = this.piano.loadIfIdle(context);
if (pianoLoad) {
void pianoLoad
.then(() => {
if (this.graph.context === context && !this.isDestroyed) {
if (this.graph.context === context && this.lifecycle !== 'destroyed') {
this.pianoEngine.cue(context.currentTime);
}
})
@ -126,7 +129,7 @@ export class GardenAudio {
context &&
(context.state === 'running' || options.userGesture === true) &&
!this.isMuted &&
!this.isDestroyed &&
this.lifecycle !== 'destroyed' &&
didChangeVibe
) {
this.playVibeChangeStinger(vibe);
@ -173,7 +176,7 @@ export class GardenAudio {
public update(snapshot: GardenAudioSnapshot): void {
const context = this.graph.context;
if (!this.hasStarted || !context || this.isMuted) {
if (this.lifecycle !== 'started' || !context || this.isMuted) {
return;
}
@ -195,7 +198,7 @@ export class GardenAudio {
}
public stroke(stroke: GardenAudioStroke): void {
if (this.isDestroyed || this.isMuted) {
if (this.lifecycle === 'destroyed' || this.isMuted) {
return;
}
@ -230,7 +233,7 @@ export class GardenAudio {
}
public async destroy(): Promise<void> {
this.isDestroyed = true;
this.lifecycle = 'destroyed';
await this.graph.close();
this.piano.reset();
@ -238,9 +241,7 @@ export class GardenAudio {
this.gestureState.reset();
this.pianoEngine.reset();
this.currentVibeId = null;
this.hasStarted = false;
this.isGestureActive = false;
this.hasQueuedPianoLoad = false;
this.lastEraserAt = Number.NEGATIVE_INFINITY;
this.lastVibeStingerAt = Number.NEGATIVE_INFINITY;
}