lgtm
This commit is contained in:
parent
ce383ce34c
commit
d2da0d1617
25 changed files with 521 additions and 1026 deletions
|
|
@ -1,5 +1,4 @@
|
|||
import type { GardenAudioEngineConfig } from '../config';
|
||||
import { clamp, clamp01 } from '../utils/clamp';
|
||||
import { clamp01 } from '../utils/clamp';
|
||||
import { ErrorHandler, Severity } from '../utils/error-handler';
|
||||
import { VibePreset } from '../vibes';
|
||||
import { GardenAudioConfig } from './garden-audio-config';
|
||||
|
|
@ -44,22 +43,13 @@ export class GardenAudio {
|
|||
private lastEraserAt = Number.NEGATIVE_INFINITY;
|
||||
private lastVibeStingerAt = Number.NEGATIVE_INFINITY;
|
||||
|
||||
public constructor(
|
||||
private readonly config: GardenAudioConfig,
|
||||
private readonly engineConfig: GardenAudioEngineConfig,
|
||||
private readonly maxMirrorSegmentCount: number
|
||||
) {
|
||||
this.graph = new GardenAudioGraph(config, engineConfig);
|
||||
this.piano = new PianoSampler(config, engineConfig, this.graph);
|
||||
this.noise = new NoiseBurstPlayer(engineConfig, this.graph);
|
||||
this.energy = new GardenAudioEnergy(engineConfig);
|
||||
this.gestureState = new GardenAudioGestureState(
|
||||
config.rhythm.speedForFullEnergyPixelsPerSecond,
|
||||
engineConfig.input
|
||||
);
|
||||
this.pianoEngine = new GenerativePianoEngine(config, engineConfig, (note) =>
|
||||
this.piano.play(note)
|
||||
);
|
||||
public constructor(private readonly config: GardenAudioConfig) {
|
||||
this.graph = new GardenAudioGraph(config);
|
||||
this.piano = new PianoSampler(config, this.graph);
|
||||
this.noise = new NoiseBurstPlayer(config, this.graph);
|
||||
this.energy = new GardenAudioEnergy(config);
|
||||
this.gestureState = new GardenAudioGestureState(config.input);
|
||||
this.pianoEngine = new GenerativePianoEngine(config, (note) => this.piano.play(note));
|
||||
}
|
||||
|
||||
public start(vibe: VibePreset, options: GardenAudioStartOptions = {}): void {
|
||||
|
|
@ -74,7 +64,7 @@ export class GardenAudio {
|
|||
|
||||
const startupRampSeconds =
|
||||
options.userGesture === true
|
||||
? this.engineConfig.muteRampSeconds
|
||||
? this.config.muteRampSeconds
|
||||
: this.config.fadeInSeconds;
|
||||
const needsResume = context.state !== 'running' && context.state !== 'closed';
|
||||
let resumePromise: Promise<void> | null = null;
|
||||
|
|
@ -148,8 +138,8 @@ export class GardenAudio {
|
|||
public setMuted(isMuted: boolean): void {
|
||||
this.isMuted = isMuted;
|
||||
this.graph.setMasterGain(
|
||||
isMuted ? this.engineConfig.muteGain : this.config.masterVolume,
|
||||
isMuted ? this.engineConfig.muteRampSeconds : this.config.fadeInSeconds
|
||||
isMuted ? this.config.muteGain : this.config.masterVolume,
|
||||
isMuted ? this.config.muteRampSeconds : this.config.fadeInSeconds
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -183,32 +173,7 @@ export class GardenAudio {
|
|||
}
|
||||
|
||||
this.selectedColorIndex = normalizeColorIndex(touch.colorIndex);
|
||||
const mirrorAmount = this.getMirrorAmount(touch.mirrorSegmentCount ?? 1);
|
||||
const pressure = this.getPressure(touch.pressure);
|
||||
const strength = clamp01(0.36 + pressure * 0.34 + mirrorAmount * 0.22);
|
||||
const frame = this.gestureState.recordTouchDown({
|
||||
touch,
|
||||
colorIndex: this.selectedColorIndex,
|
||||
mirrorAmount,
|
||||
pressure,
|
||||
strength,
|
||||
});
|
||||
|
||||
this.energy.recordStroke(strength, context.currentTime);
|
||||
this.pianoEngine.recordTouchDown({
|
||||
vibe: touch.vibe,
|
||||
now: context.currentTime,
|
||||
strength,
|
||||
selectedColorIndex: this.selectedColorIndex,
|
||||
mirrorAmount,
|
||||
panBias: frame.panBias,
|
||||
registerBias: frame.registerBias,
|
||||
brightnessBias: frame.brightnessBias,
|
||||
contour: frame.contour,
|
||||
pressureAmount: frame.pressure,
|
||||
pressureDelta: frame.pressureDelta,
|
||||
maniaAmount: frame.maniaAmount,
|
||||
});
|
||||
this.gestureState.recordTouchDown();
|
||||
}
|
||||
|
||||
public update(snapshot: GardenAudioSnapshot): void {
|
||||
|
|
@ -248,37 +213,25 @@ export class GardenAudio {
|
|||
return;
|
||||
}
|
||||
|
||||
const metrics = getStrokeMetrics(
|
||||
stroke,
|
||||
this.config.rhythm.speedForFullEnergyPixelsPerSecond,
|
||||
this.engineConfig.input
|
||||
);
|
||||
const metrics = getStrokeMetrics(stroke, this.config.input);
|
||||
const now = context.currentTime;
|
||||
|
||||
this.selectedColorIndex = normalizeColorIndex(stroke.colorIndex);
|
||||
const frame = this.gestureState.recordStroke({ metrics });
|
||||
const strokeEnergy = frame.activity;
|
||||
|
||||
if (stroke.isErasing) {
|
||||
this.energy.recordEraserStroke();
|
||||
this.playEraser(stroke, metrics.speedAmount, metrics.pressure, now);
|
||||
this.playEraser(strokeEnergy, now);
|
||||
return;
|
||||
}
|
||||
|
||||
const mirrorAmount = this.getMirrorAmount(stroke.mirrorSegmentCount ?? 1);
|
||||
const frame = this.gestureState.recordStroke({ stroke, metrics, mirrorAmount });
|
||||
const strokeEnergy = frame.activity;
|
||||
this.energy.recordStroke(strokeEnergy, now);
|
||||
this.pianoEngine.recordStroke({
|
||||
vibe: stroke.vibe,
|
||||
now,
|
||||
activity: strokeEnergy,
|
||||
selectedColorIndex: this.selectedColorIndex,
|
||||
mirrorAmount,
|
||||
panBias: frame.panBias,
|
||||
registerBias: frame.registerBias,
|
||||
brightnessBias: frame.brightnessBias,
|
||||
contour: frame.contour,
|
||||
pressureAmount: frame.pressure,
|
||||
pressureDelta: frame.pressureDelta,
|
||||
maniaAmount: frame.maniaAmount,
|
||||
});
|
||||
}
|
||||
|
|
@ -307,10 +260,7 @@ export class GardenAudio {
|
|||
}
|
||||
|
||||
const now = context.currentTime;
|
||||
if (
|
||||
now - this.lastVibeStingerAt <
|
||||
this.engineConfig.vibeChangeStingerMinIntervalSeconds
|
||||
) {
|
||||
if (now - this.lastVibeStingerAt < this.config.vibeChangeStingerMinIntervalSeconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -318,46 +268,29 @@ export class GardenAudio {
|
|||
this.pianoEngine.playVibeChangeStinger(vibe, now);
|
||||
}
|
||||
|
||||
private playEraser(
|
||||
stroke: GardenAudioStroke,
|
||||
speedAmount: number,
|
||||
pressure: number,
|
||||
now: number
|
||||
): void {
|
||||
private playEraser(activity: number, now: number): void {
|
||||
if (!this.graph.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sizeAmount = clamp01(
|
||||
(stroke.eraserSizePixels ?? this.engineConfig.eraser.defaultSizePixels) /
|
||||
Math.max(
|
||||
1,
|
||||
stroke.canvasSize[0] * this.engineConfig.eraser.canvasWidthRatioForFullSize
|
||||
)
|
||||
);
|
||||
const x = clamp01(stroke.to[0] / Math.max(1, stroke.canvasSize[0]));
|
||||
const distanceActivity = clamp01(activity);
|
||||
if (distanceActivity <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterHz =
|
||||
this.config.eraser.filterMinHz +
|
||||
(this.config.eraser.filterMaxHz - this.config.eraser.filterMinHz) *
|
||||
clamp01(
|
||||
speedAmount * this.engineConfig.eraser.filterSpeedWeight +
|
||||
pressure * this.engineConfig.eraser.filterPressureWeight +
|
||||
sizeAmount * this.engineConfig.eraser.filterSizeWeight
|
||||
);
|
||||
distanceActivity;
|
||||
|
||||
if (now - this.lastEraserAt >= this.config.eraser.minIntervalSeconds) {
|
||||
this.lastEraserAt = now;
|
||||
this.noise.play({
|
||||
startTime: now,
|
||||
durationSeconds: this.engineConfig.eraser.durationSeconds,
|
||||
gain:
|
||||
this.config.eraser.noiseGain *
|
||||
(this.engineConfig.eraser.gainBase +
|
||||
speedAmount * this.engineConfig.eraser.gainSpeedWeight +
|
||||
pressure * this.engineConfig.eraser.gainPressureWeight +
|
||||
sizeAmount * this.engineConfig.eraser.gainSizeWeight),
|
||||
durationSeconds: this.config.eraser.durationSeconds,
|
||||
gain: this.config.eraser.noiseGain * distanceActivity,
|
||||
filterHz,
|
||||
pan: clamp(x * 2 - 1, -1, 1),
|
||||
pan: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -370,7 +303,7 @@ export class GardenAudio {
|
|||
|
||||
const profile = getVibeProfile(this.config, snapshot.vibe);
|
||||
const activity = snapshot.isErasing
|
||||
? this.engineConfig.delay.erasingActivity
|
||||
? this.config.delay.erasingActivity
|
||||
: this.energy.getLevel();
|
||||
this.graph.updateDelay(profile, activity);
|
||||
}
|
||||
|
|
@ -384,27 +317,4 @@ export class GardenAudio {
|
|||
this.graph.applyDelayProfile(getVibeProfile(this.config, vibe));
|
||||
this.pianoEngine.cue(this.graph.context.currentTime);
|
||||
}
|
||||
|
||||
private getMirrorAmount(mirrorSegmentCount: number): number {
|
||||
const maxMirrorSegmentCount = Math.max(1, this.maxMirrorSegmentCount);
|
||||
const segmentCount = clamp(
|
||||
Number.isFinite(mirrorSegmentCount) ? mirrorSegmentCount : 1,
|
||||
1,
|
||||
maxMirrorSegmentCount
|
||||
);
|
||||
|
||||
if (maxMirrorSegmentCount <= 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return clamp01((segmentCount - 1) / (maxMirrorSegmentCount - 1));
|
||||
}
|
||||
|
||||
private getPressure(pressure: number | undefined): number {
|
||||
if (pressure !== undefined && Number.isFinite(pressure) && pressure > 0) {
|
||||
return clamp01(pressure);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue