clean up
This commit is contained in:
parent
d6a8f898d1
commit
15e99380b5
84 changed files with 1659 additions and 4726 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { clamp01 } from '../utils/clamp';
|
||||
import { ErrorHandler, Severity } from '../utils/error-handler';
|
||||
import { clamp01 } from '../utils/math';
|
||||
import type { VibeId, VibePreset } from '../vibes';
|
||||
import { GardenAudioConfig } from './garden-audio-config';
|
||||
import type { GardenAudioConfig } from './garden-audio-config';
|
||||
import { GardenAudioEnergy } from './garden-audio-energy';
|
||||
import { GardenAudioGestureState } from './garden-audio-gesture-state';
|
||||
import { GardenAudioGraph } from './garden-audio-graph';
|
||||
|
|
@ -24,6 +24,12 @@ export type {
|
|||
|
||||
type AudioLifecycle = 'idle' | 'started' | 'destroyed';
|
||||
|
||||
const muteGain = 0.0001;
|
||||
const muteRampSeconds = 0.02;
|
||||
const brushUpPianoFinishSeconds = 1.2;
|
||||
const brushUpPianoFadeSeconds = 1.1;
|
||||
const vibeChangeStingerMinIntervalSeconds = 0.45;
|
||||
|
||||
export class GardenAudio {
|
||||
private readonly graph: GardenAudioGraph;
|
||||
private readonly piano: PianoSampler;
|
||||
|
|
@ -36,7 +42,10 @@ export class GardenAudio {
|
|||
private lifecycle: AudioLifecycle = 'idle';
|
||||
private isMuted = false;
|
||||
private isGestureActive = false;
|
||||
private isPianoStoppedAfterGesture = false;
|
||||
private fadePianoAfter: number | null = null;
|
||||
private masterVolume: number;
|
||||
private stopPianoAfter: number | null = null;
|
||||
private lastEraserAt = Number.NEGATIVE_INFINITY;
|
||||
private lastVibeStingerAt = Number.NEGATIVE_INFINITY;
|
||||
|
||||
|
|
@ -44,50 +53,55 @@ export class GardenAudio {
|
|||
this.masterVolume = clamp01(config.masterVolume);
|
||||
this.graph = new GardenAudioGraph(config);
|
||||
this.piano = new PianoSampler(config, this.graph);
|
||||
this.noise = new NoiseBurstPlayer(config, this.graph);
|
||||
this.noise = new NoiseBurstPlayer(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 {
|
||||
if (this.lifecycle === 'destroyed' || this.isMuted) {
|
||||
const isUserGesture = options.userGesture === true;
|
||||
|
||||
if (this.lifecycle === 'destroyed') {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = this.graph.ensureContext(options.userGesture === true);
|
||||
const context = this.graph.ensureContext(isUserGesture);
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startupRampSeconds =
|
||||
options.userGesture === true
|
||||
? this.config.muteRampSeconds
|
||||
: this.config.fadeInSeconds;
|
||||
const startupRampSeconds = isUserGesture
|
||||
? muteRampSeconds
|
||||
: this.config.fadeInSeconds;
|
||||
const needsResume = context.state !== 'running' && context.state !== 'closed';
|
||||
let resumePromise: Promise<void> | null = null;
|
||||
|
||||
if (isUserGesture) {
|
||||
this.graph.startMediaElementOutput();
|
||||
this.graph.unlock();
|
||||
}
|
||||
|
||||
if (needsResume) {
|
||||
if (options.userGesture !== true) {
|
||||
if (!isUserGesture) {
|
||||
return;
|
||||
}
|
||||
resumePromise = context.resume();
|
||||
}
|
||||
|
||||
if (options.userGesture === true) {
|
||||
if (isUserGesture) {
|
||||
this.graph.unlock();
|
||||
}
|
||||
|
||||
if (resumePromise) {
|
||||
void resumePromise
|
||||
.then(() => {
|
||||
if (
|
||||
this.graph.context === context &&
|
||||
this.lifecycle !== 'destroyed' &&
|
||||
!this.isMuted
|
||||
) {
|
||||
if (this.graph.context === context && this.lifecycle !== 'destroyed') {
|
||||
this.graph.unlock();
|
||||
this.graph.setMasterGain(this.masterVolume, startupRampSeconds);
|
||||
this.completeStart(vibe, {
|
||||
context,
|
||||
startupRampSeconds,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -96,6 +110,32 @@ export class GardenAudio {
|
|||
severity: Severity.WARNING,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.completeStart(vibe, {
|
||||
context,
|
||||
startupRampSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
private completeStart(
|
||||
vibe: VibePreset,
|
||||
{
|
||||
context,
|
||||
startupRampSeconds,
|
||||
}: {
|
||||
context: AudioContext;
|
||||
startupRampSeconds: number;
|
||||
}
|
||||
): void {
|
||||
if (this.graph.context !== context || this.lifecycle === 'destroyed') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isMuted) {
|
||||
this.graph.setMasterGain(muteGain, muteRampSeconds);
|
||||
return;
|
||||
}
|
||||
|
||||
this.lifecycle = 'started';
|
||||
|
|
@ -111,7 +151,12 @@ export class GardenAudio {
|
|||
this.pianoEngine.cue(context.currentTime);
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
.catch((error) => {
|
||||
ErrorHandler.addException(error, {
|
||||
fallbackMessage: 'Could not load piano samples. Using synthesized audio.',
|
||||
severity: Severity.WARNING,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,8 +188,8 @@ export class GardenAudio {
|
|||
|
||||
this.isMuted = isMuted;
|
||||
this.graph.setMasterGain(
|
||||
isMuted ? this.config.muteGain : this.masterVolume,
|
||||
isMuted ? this.config.muteRampSeconds : this.config.fadeInSeconds
|
||||
isMuted ? muteGain : this.masterVolume,
|
||||
isMuted ? muteRampSeconds : this.config.fadeInSeconds
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -162,6 +207,9 @@ export class GardenAudio {
|
|||
}
|
||||
|
||||
this.isGestureActive = true;
|
||||
this.isPianoStoppedAfterGesture = false;
|
||||
this.fadePianoAfter = null;
|
||||
this.stopPianoAfter = null;
|
||||
this.gestureState.beginGesture();
|
||||
this.energy.beginGesture(context.currentTime);
|
||||
this.pianoEngine.beginGesture();
|
||||
|
|
@ -170,6 +218,12 @@ export class GardenAudio {
|
|||
public endGesture(): void {
|
||||
this.gestureState.endGesture();
|
||||
this.isGestureActive = false;
|
||||
const context = this.graph.context;
|
||||
this.isPianoStoppedAfterGesture = true;
|
||||
this.fadePianoAfter = context
|
||||
? context.currentTime + brushUpPianoFinishSeconds
|
||||
: null;
|
||||
this.stopPianoAfter = null;
|
||||
this.energy.endGesture();
|
||||
this.pianoEngine.endGesture();
|
||||
}
|
||||
|
|
@ -187,6 +241,21 @@ export class GardenAudio {
|
|||
this.energy.silence();
|
||||
}
|
||||
|
||||
if (!this.isGestureActive && this.isPianoStoppedAfterGesture) {
|
||||
if (this.fadePianoAfter !== null && context.currentTime >= this.fadePianoAfter) {
|
||||
this.piano.fadeAll(brushUpPianoFadeSeconds);
|
||||
this.fadePianoAfter = null;
|
||||
this.stopPianoAfter = context.currentTime + brushUpPianoFadeSeconds;
|
||||
}
|
||||
if (this.stopPianoAfter !== null && context.currentTime >= this.stopPianoAfter) {
|
||||
this.piano.stopAll();
|
||||
this.pianoEngine.reset();
|
||||
this.stopPianoAfter = null;
|
||||
}
|
||||
this.updateDelay(snapshot);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pianoEngine.renderLookahead({
|
||||
vibe: snapshot.vibe,
|
||||
now: context.currentTime,
|
||||
|
|
@ -211,7 +280,7 @@ export class GardenAudio {
|
|||
return;
|
||||
}
|
||||
|
||||
const metrics = getStrokeMetrics(stroke, this.config.input);
|
||||
const metrics = getStrokeMetrics(stroke);
|
||||
const now = context.currentTime;
|
||||
|
||||
const frame = this.gestureState.recordStroke({ metrics });
|
||||
|
|
@ -242,6 +311,9 @@ export class GardenAudio {
|
|||
this.pianoEngine.reset();
|
||||
this.currentVibeId = null;
|
||||
this.isGestureActive = false;
|
||||
this.isPianoStoppedAfterGesture = false;
|
||||
this.fadePianoAfter = null;
|
||||
this.stopPianoAfter = null;
|
||||
this.lastEraserAt = Number.NEGATIVE_INFINITY;
|
||||
this.lastVibeStingerAt = Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
|
@ -253,7 +325,7 @@ export class GardenAudio {
|
|||
}
|
||||
|
||||
const now = context.currentTime;
|
||||
if (now - this.lastVibeStingerAt < this.config.vibeChangeStingerMinIntervalSeconds) {
|
||||
if (now - this.lastVibeStingerAt < vibeChangeStingerMinIntervalSeconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue