fleeting-garden/src/audio/garden-audio-energy.ts
Andras Schmelczer 2347ecd201
Some checks failed
Deploy to Pages / build (pull_request) Failing after 3m15s
.
2026-05-13 22:13:15 +01:00

83 lines
2.2 KiB
TypeScript

import type { GardenAudioEngineConfig } from '../config';
import { clamp01 } from '../utils/clamp';
const STROKE_IMMEDIATE_ACTIVITY_SCALE = 0.85;
export class GardenAudioEnergy {
private isGestureActive = false;
private energy = 0;
private targetEnergy = 0;
private lastEnergyUpdateAt = 0;
public constructor(private readonly engineConfig: GardenAudioEngineConfig) {}
public beginGesture(now: number): void {
this.isGestureActive = true;
this.lastEnergyUpdateAt = now;
}
public endGesture(): void {
this.isGestureActive = false;
this.targetEnergy = 0;
}
public recordStroke(strokeEnergy: number, now: number): void {
const energy = clamp01(strokeEnergy);
this.targetEnergy = Math.max(this.targetEnergy, energy);
if (this.isGestureActive) {
this.energy = Math.max(this.energy, energy * STROKE_IMMEDIATE_ACTIVITY_SCALE);
}
this.lastEnergyUpdateAt ||= now;
}
public recordEraserStroke(): void {
this.targetEnergy = 0;
}
public silence(): void {
this.targetEnergy = 0;
this.energy = 0;
}
public update(now: number): void {
if (this.lastEnergyUpdateAt <= 0) {
this.lastEnergyUpdateAt = now;
return;
}
const elapsedSeconds = Math.max(0, now - this.lastEnergyUpdateAt);
this.lastEnergyUpdateAt = now;
this.targetEnergy *= Math.exp(
-elapsedSeconds / this.engineConfig.energy.strokeDecaySeconds
);
const target = this.isGestureActive ? this.targetEnergy : 0;
let timeConstant = this.engineConfig.energy.decaySeconds;
if (!this.isGestureActive) {
timeConstant = this.engineConfig.energy.releaseSeconds;
} else if (target > this.energy) {
timeConstant = this.engineConfig.energy.attackSeconds;
}
const amount = 1 - Math.exp(-elapsedSeconds / timeConstant);
this.energy += (target - this.energy) * amount;
}
public getActivity(): number {
if (!this.isGestureActive) {
return 0;
}
return this.getLevel();
}
public getLevel(): number {
return clamp01(this.energy);
}
public reset(): void {
this.isGestureActive = false;
this.energy = 0;
this.targetEnergy = 0;
this.lastEnergyUpdateAt = 0;
}
}