This commit is contained in:
Andras Schmelczer 2026-05-18 08:11:52 +01:00
commit 15e99380b5
84 changed files with 1659 additions and 4726 deletions

View file

@ -1,18 +1,28 @@
import { clamp, clamp01 } from '../utils/clamp';
import { VibePreset } from '../vibes';
import {
import { clamp, clamp01 } from '../utils/math';
import type { VibePreset } from '../vibes';
import type {
GardenAudioChord,
GardenAudioConfig,
GardenAudioRegister,
GardenAudioStylePool,
GardenAudioVibeProfile,
} from './garden-audio-config';
import {
degreeToSemitone,
getChordIntervals,
getVibeProfile,
PITCH_SEMITONES_PER_OCTAVE,
} from './garden-audio-music';
import { PianoNote } from './garden-audio-types';
import {
GENERATIVE_LOOKAHEAD_SECONDS,
GENERATIVE_START_DELAY_SECONDS,
PIANO_SCHEDULE_AHEAD_SECONDS,
} from './garden-audio-scheduling';
import type { PianoNote } from './garden-audio-types';
import {
generativePianoTuning,
styleVoices,
type GardenAudioRegister,
type GardenAudioStylePool,
} from './generative-piano-tuning';
type GardenAudioStyleIndex = 0 | 1 | 2;
@ -87,8 +97,8 @@ export class GenerativePianoEngine {
private readonly playNote: (note: PianoNote) => void
) {}
private get generation(): GardenAudioConfig['generativePiano'] {
return this.config.generativePiano;
private get generation(): typeof generativePianoTuning {
return generativePianoTuning;
}
public prime(now: number): void {
@ -190,7 +200,7 @@ export class GenerativePianoEngine {
vibe,
now,
activity,
lookaheadSeconds = this.config.rhythm.lookaheadSeconds,
lookaheadSeconds = GENERATIVE_LOOKAHEAD_SECONDS,
}: RenderLookaheadRequest): void {
this.prime(now);
this.skipLateBeats(now);
@ -418,7 +428,7 @@ export class GenerativePianoEngine {
velocity:
(this.generation.supportNote.velocityBase +
expression * this.generation.supportNote.velocityExpressionWeight) *
this.config.styleVoices[styleIndex].velocityMultiplier,
styleVoices[styleIndex].velocityMultiplier,
startTime,
durationSeconds:
this.generation.supportNote.durationBaseSeconds +
@ -464,7 +474,7 @@ export class GenerativePianoEngine {
velocity:
(this.generation.textureNote.velocityBase +
expression * this.generation.textureNote.velocityExpressionWeight) *
this.config.styleVoices[styleIndex].velocityMultiplier,
styleVoices[styleIndex].velocityMultiplier,
startTime,
durationSeconds:
this.generation.textureNote.durationBaseSeconds +
@ -511,7 +521,7 @@ export class GenerativePianoEngine {
velocity:
(this.generation.gestureAccent.velocityBase +
strength * this.generation.gestureAccent.velocityStrengthWeight) *
this.config.styleVoices[styleIndex].velocityMultiplier,
styleVoices[styleIndex].velocityMultiplier,
startTime,
durationSeconds:
this.generation.gestureAccent.durationBaseSeconds +
@ -560,7 +570,7 @@ export class GenerativePianoEngine {
velocity:
(this.generation.touchNote.velocityBase +
strength * this.generation.touchNote.velocityStrengthWeight) *
this.config.styleVoices[styleIndex].velocityMultiplier,
styleVoices[styleIndex].velocityMultiplier,
startTime: now,
durationSeconds:
this.generation.touchNote.durationBaseSeconds +
@ -661,7 +671,7 @@ export class GenerativePianoEngine {
lookaheadEnd: number;
activity: number;
}): void {
const earliestStart = now + this.config.piano.scheduleAheadSeconds;
const earliestStart = now + PIANO_SCHEDULE_AHEAD_SECONDS;
this.nextBrushStreamStep ??= 0;
this.pruneBrushStreamNoteCounts(this.getGlobalBarIndex(now) - 1);
@ -770,7 +780,7 @@ export class GenerativePianoEngine {
velocity:
(this.generation.brushStream.velocityBase +
intensity * this.generation.brushStream.velocityIntensityWeight) *
this.config.styleVoices[styleIndex].velocityMultiplier,
styleVoices[styleIndex].velocityMultiplier,
startTime,
durationSeconds,
pan,
@ -803,7 +813,7 @@ export class GenerativePianoEngine {
velocity:
(this.generation.brushStreamEcho.velocityBase +
intensity * this.generation.brushStreamEcho.velocityIntensityWeight) *
this.config.styleVoices[styleIndex].velocityMultiplier,
styleVoices[styleIndex].velocityMultiplier,
startTime: startTime + this.generation.brushMotifCanonDelaySeconds,
durationSeconds: Math.max(
this.generation.brushStreamEcho.durationMinSeconds,
@ -872,8 +882,8 @@ export class GenerativePianoEngine {
: intensity >= this.generation.brushStream.intenseThreshold
? this.generation.brushStreamIntenseIntervalBeats
: intensity >= this.generation.brushStream.activeThreshold
? this.generation.brushStreamActiveIntervalBeats
: this.generation.brushStreamIdleIntervalBeats;
? this.generation.brushStreamActiveIntervalBeats
: this.generation.brushStreamIdleIntervalBeats;
return Math.max(1, Math.round(intervalBeats * this.config.rhythm.stepsPerBeat));
}
@ -913,7 +923,7 @@ export class GenerativePianoEngine {
pool: GardenAudioStylePool;
styleIndex: GardenAudioStyleIndex;
}): Array<number> {
const styleOffset = this.config.styleVoices[styleIndex].scaleDegreeOffset;
const styleOffset = styleVoices[styleIndex].scaleDegreeOffset;
if (!layer || layer.motifOffsets.length === 0) {
return this.rotate(pool.scaleDegrees, this.brushStreamNoteIndex + styleOffset);
}
@ -990,10 +1000,7 @@ export class GenerativePianoEngine {
octave <= this.generation.candidateOctaveSearch.max;
octave += 1
) {
const midi =
pitchSource.baseMidi +
offset +
octave * this.config.piano.pitchSemitonesPerOctave;
const midi = pitchSource.baseMidi + offset + octave * PITCH_SEMITONES_PER_OCTAVE;
if (midi >= register.midiMin && midi <= register.midiMax) {
const roundedMidi = Math.round(midi);
candidates.push({
@ -1080,7 +1087,7 @@ export class GenerativePianoEngine {
private getStylePan(styleIndex: GardenAudioStyleIndex): number {
const pool = this.generation.stylePools[styleIndex];
const styleVoice = this.config.styleVoices[styleIndex];
const styleVoice = styleVoices[styleIndex];
return clamp(
pool.pan + styleVoice.panOffset * this.generation.stylePanOffsetScale,
-1,
@ -1113,7 +1120,7 @@ export class GenerativePianoEngine {
return;
}
const earliestStart = now + this.config.piano.scheduleAheadSeconds;
const earliestStart = now + PIANO_SCHEDULE_AHEAD_SECONDS;
if (this.getTimeForStep(this.nextBeatStep) >= earliestStart) {
return;
}
@ -1152,7 +1159,7 @@ export class GenerativePianoEngine {
private getTimeForStep(stepIndex: number): number {
return (
(this.timelineStartedAt ?? 0) +
this.config.startDelaySeconds +
GENERATIVE_START_DELAY_SECONDS +
stepIndex * this.getStepDurationSeconds()
);
}
@ -1161,7 +1168,7 @@ export class GenerativePianoEngine {
const timelineStartedAt = this.timelineStartedAt ?? startTime;
const elapsedSeconds = Math.max(
0,
startTime - timelineStartedAt - this.config.startDelaySeconds
startTime - timelineStartedAt - GENERATIVE_START_DELAY_SECONDS
);
return Math.floor(elapsedSeconds / this.getStepDurationSeconds());
}
@ -1170,7 +1177,7 @@ export class GenerativePianoEngine {
const timelineStartedAt = this.timelineStartedAt ?? startTime;
const elapsedSeconds = Math.max(
0,
startTime - timelineStartedAt - this.config.startDelaySeconds
startTime - timelineStartedAt - GENERATIVE_START_DELAY_SECONDS
);
return Math.max(
0,