lgtm
This commit is contained in:
parent
ce383ce34c
commit
d2da0d1617
25 changed files with 521 additions and 1026 deletions
|
|
@ -1,9 +1,10 @@
|
|||
import type { GardenAudioEngineConfig } from '../config';
|
||||
import { clamp, clamp01 } from '../utils/clamp';
|
||||
import { VibePreset } from '../vibes';
|
||||
import {
|
||||
GardenAudioChord,
|
||||
GardenAudioColorPool,
|
||||
GardenAudioConfig,
|
||||
GardenAudioRegister,
|
||||
GardenAudioVibeProfile,
|
||||
} from './garden-audio-config';
|
||||
import {
|
||||
|
|
@ -51,17 +52,6 @@ interface TouchDownRequest {
|
|||
maniaAmount?: number;
|
||||
}
|
||||
|
||||
interface Register {
|
||||
midiMin: number;
|
||||
midiMax: number;
|
||||
preferredMidi: number;
|
||||
pan: number;
|
||||
}
|
||||
|
||||
interface ColorPool extends Register {
|
||||
scaleDegrees: ReadonlyArray<number>;
|
||||
}
|
||||
|
||||
interface PitchCandidate {
|
||||
midi: number;
|
||||
preference: number;
|
||||
|
|
@ -89,81 +79,6 @@ interface BrushPhraseLayer {
|
|||
maniaAmount: number;
|
||||
}
|
||||
|
||||
const COLOR_POOLS: [ColorPool, ColorPool, ColorPool] = [
|
||||
{
|
||||
midiMin: 48,
|
||||
midiMax: 67,
|
||||
preferredMidi: 55,
|
||||
pan: -0.18,
|
||||
scaleDegrees: [0, 1, 2, 4],
|
||||
},
|
||||
{
|
||||
midiMin: 55,
|
||||
midiMax: 74,
|
||||
preferredMidi: 63,
|
||||
pan: 0,
|
||||
scaleDegrees: [1, 2, 3, 5],
|
||||
},
|
||||
{
|
||||
midiMin: 62,
|
||||
midiMax: 81,
|
||||
preferredMidi: 72,
|
||||
pan: 0.18,
|
||||
scaleDegrees: [2, 3, 4, 6],
|
||||
},
|
||||
];
|
||||
|
||||
const PAD_REGISTERS: [Register, Register, Register] = [
|
||||
{
|
||||
midiMin: 40,
|
||||
midiMax: 55,
|
||||
preferredMidi: 48,
|
||||
pan: -0.12,
|
||||
},
|
||||
{
|
||||
midiMin: 48,
|
||||
midiMax: 64,
|
||||
preferredMidi: 55,
|
||||
pan: 0.08,
|
||||
},
|
||||
{
|
||||
midiMin: 58,
|
||||
midiMax: 76,
|
||||
preferredMidi: 67,
|
||||
pan: 0.2,
|
||||
},
|
||||
];
|
||||
|
||||
const CHORD_BARS = 4;
|
||||
const SUPPORT_BAR_SPACING = 2;
|
||||
const SUPPORT_BAR_OFFSET = 1;
|
||||
const IDLE_TEXTURE_BAR_SPACING = 2;
|
||||
const MEDIUM_TEXTURE_BAR_SPACING = 1;
|
||||
const TEXTURE_BEAT = 2;
|
||||
const HIGH_ACTIVITY_EXTRA_BEAT = 3;
|
||||
const HIGH_ACTIVITY_EXTRA_THRESHOLD = 0.45;
|
||||
const NOTE_SCORE_PREFERENCE_WEIGHT = 1.8;
|
||||
const NOTE_SCORE_REGISTER_WEIGHT = 0.28;
|
||||
const NOTE_SCORE_REPEAT_PENALTY = 3.2;
|
||||
const GESTURE_ACCENT_SPACING_SECONDS = 0.26;
|
||||
const GESTURE_ACCENT_MIN_INTERVAL_SECONDS = 2.5;
|
||||
const STROKE_ACCENT_MIN_INTERVAL_SECONDS = 3.2;
|
||||
const STROKE_ACCENT_THRESHOLD = 0.58;
|
||||
const STINGER_SPACING_SECONDS = 0.08;
|
||||
const STINGER_DURATION_SECONDS = 1.1;
|
||||
const MAX_BRUSH_PHRASE_LAYERS = 5;
|
||||
const BRUSH_LAYER_BASE_SECONDS = 5.5;
|
||||
const BRUSH_LAYER_ENERGY_SECONDS = 2.5;
|
||||
const BRUSH_LAYER_MIRROR_SECONDS = 3;
|
||||
const BRUSH_LAYER_MIN_INTENSITY = 0.08;
|
||||
const BRUSH_STREAM_IDLE_INTERVAL_BEATS = 2;
|
||||
const BRUSH_STREAM_ACTIVE_INTERVAL_BEATS = 1;
|
||||
const BRUSH_STREAM_INTENSE_INTERVAL_BEATS = 0.5;
|
||||
const BRUSH_STREAM_MANIC_INTERVAL_BEATS = 0.25;
|
||||
const BRUSH_MOTIF_MAX_STEPS = 8;
|
||||
const BRUSH_MOTIF_CANON_DELAY_SECONDS = 0.055;
|
||||
const PAD_DURATION_BAR_SCALE = 0.46;
|
||||
|
||||
export class GenerativePianoEngine {
|
||||
private nextBeatAt: number | null = null;
|
||||
private timelineStartedAt: number | null = null;
|
||||
|
|
@ -183,23 +98,26 @@ export class GenerativePianoEngine {
|
|||
|
||||
public constructor(
|
||||
private readonly config: GardenAudioConfig,
|
||||
private readonly engineConfig: GardenAudioEngineConfig,
|
||||
private readonly playNote: (note: PianoNote) => void
|
||||
) {}
|
||||
|
||||
private get generation(): GardenAudioConfig['generativePiano'] {
|
||||
return this.config.generativePiano;
|
||||
}
|
||||
|
||||
public prime(now: number): void {
|
||||
if (this.nextBeatAt === null) {
|
||||
this.nextBeatAt = now + this.engineConfig.startDelaySeconds;
|
||||
this.nextBeatAt = now + this.config.startDelaySeconds;
|
||||
}
|
||||
this.timelineStartedAt ??= now;
|
||||
this.nextBrushStreamAt ??= now + this.engineConfig.startDelaySeconds;
|
||||
this.nextBrushStreamAt ??= now + this.config.startDelaySeconds;
|
||||
}
|
||||
|
||||
public cue(now: number): void {
|
||||
this.nextBeatAt = now + this.engineConfig.startDelaySeconds;
|
||||
this.nextBeatAt = now + this.config.startDelaySeconds;
|
||||
this.timelineStartedAt = now;
|
||||
this.beatIndex = 0;
|
||||
this.nextBrushStreamAt = now + this.engineConfig.startDelaySeconds;
|
||||
this.nextBrushStreamAt = now + this.config.startDelaySeconds;
|
||||
this.brushStreamNoteIndex = 0;
|
||||
this.lastBrushStreamMidi = null;
|
||||
}
|
||||
|
|
@ -288,7 +206,7 @@ export class GenerativePianoEngine {
|
|||
|
||||
if (
|
||||
this.isWaitingForGestureAccent &&
|
||||
now - this.lastGestureAccentAt >= GESTURE_ACCENT_MIN_INTERVAL_SECONDS
|
||||
now - this.lastGestureAccentAt >= this.generation.gestureAccentMinIntervalSeconds
|
||||
) {
|
||||
this.recordTouchDown({
|
||||
vibe,
|
||||
|
|
@ -310,8 +228,8 @@ export class GenerativePianoEngine {
|
|||
...normalizedMotif,
|
||||
});
|
||||
if (
|
||||
strength >= STROKE_ACCENT_THRESHOLD &&
|
||||
now - this.lastStrokeAccentAt >= STROKE_ACCENT_MIN_INTERVAL_SECONDS
|
||||
strength >= this.generation.strokeAccentThreshold &&
|
||||
now - this.lastStrokeAccentAt >= this.generation.strokeAccentMinIntervalSeconds
|
||||
) {
|
||||
this.lastStrokeAccentAt = now;
|
||||
this.playGestureAccent(vibe, now, selectedColorIndex, strength, 1);
|
||||
|
|
@ -361,7 +279,10 @@ export class GenerativePianoEngine {
|
|||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const notes = [
|
||||
{
|
||||
midi: this.chooseMidi({ baseMidi: rootMidi, offsets: [0] }, PAD_REGISTERS[0]),
|
||||
midi: this.chooseMidi(
|
||||
{ baseMidi: rootMidi, offsets: [0] },
|
||||
this.generation.padRegisters[0]
|
||||
),
|
||||
velocity: 0.1,
|
||||
pan: -0.16,
|
||||
delaySend: 0.012,
|
||||
|
|
@ -369,7 +290,7 @@ export class GenerativePianoEngine {
|
|||
{
|
||||
midi: this.chooseMidi(
|
||||
{ baseMidi: rootMidi, offsets: [intervals[1], intervals[2]] },
|
||||
PAD_REGISTERS[1]
|
||||
this.generation.padRegisters[1]
|
||||
),
|
||||
velocity: 0.085,
|
||||
pan: 0,
|
||||
|
|
@ -378,7 +299,7 @@ export class GenerativePianoEngine {
|
|||
{
|
||||
midi: this.chooseMidi(
|
||||
{ baseMidi: rootMidi, offsets: [intervals[3], intervals[2]] },
|
||||
PAD_REGISTERS[2]
|
||||
this.generation.padRegisters[2]
|
||||
),
|
||||
velocity: 0.07,
|
||||
pan: 0.16,
|
||||
|
|
@ -389,9 +310,9 @@ export class GenerativePianoEngine {
|
|||
notes.forEach((note, index) => {
|
||||
this.playNote({
|
||||
...note,
|
||||
durationSeconds: STINGER_DURATION_SECONDS,
|
||||
durationSeconds: this.generation.stingerDurationSeconds,
|
||||
lowpassHz: this.getLowpassHz(profile, note.midi, 0.35),
|
||||
startTime: now + index * STINGER_SPACING_SECONDS,
|
||||
startTime: now + index * this.generation.stingerSpacingSeconds,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -429,7 +350,7 @@ export class GenerativePianoEngine {
|
|||
const beatInBar = beatIndex % beatsPerBar;
|
||||
const barIndex = Math.floor(beatIndex / beatsPerBar);
|
||||
|
||||
if (beatInBar === 0 && barIndex % CHORD_BARS === 0) {
|
||||
if (beatInBar === 0 && barIndex % this.generation.chordBars === 0) {
|
||||
this.playPadChord(profile, barIndex, startTime, expression);
|
||||
}
|
||||
|
||||
|
|
@ -437,13 +358,16 @@ export class GenerativePianoEngine {
|
|||
this.playSupportNote(profile, barIndex, startTime, expression, selectedColorIndex);
|
||||
}
|
||||
|
||||
if (beatInBar === TEXTURE_BEAT && this.shouldPlayTexture(expression, barIndex)) {
|
||||
if (
|
||||
beatInBar === this.generation.textureBeat &&
|
||||
this.shouldPlayTexture(expression, barIndex)
|
||||
) {
|
||||
this.playTextureNote(profile, barIndex, startTime, expression, selectedColorIndex);
|
||||
}
|
||||
|
||||
if (
|
||||
beatInBar === HIGH_ACTIVITY_EXTRA_BEAT &&
|
||||
expression >= HIGH_ACTIVITY_EXTRA_THRESHOLD
|
||||
beatInBar === this.generation.highActivityExtraBeat &&
|
||||
expression >= this.generation.highActivityExtraThreshold
|
||||
) {
|
||||
this.playTextureNote(
|
||||
profile,
|
||||
|
|
@ -464,21 +388,24 @@ export class GenerativePianoEngine {
|
|||
const chord = this.getChord(profile, barIndex);
|
||||
const intervals = getChordIntervals(chord, true);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const durationSeconds = this.getBarDurationSeconds() * CHORD_BARS * PAD_DURATION_BAR_SCALE;
|
||||
const durationSeconds =
|
||||
this.getBarDurationSeconds() *
|
||||
this.generation.chordBars *
|
||||
this.generation.padDurationBarScale;
|
||||
const notes = [
|
||||
{
|
||||
source: { baseMidi: rootMidi, offsets: [0] },
|
||||
register: PAD_REGISTERS[0],
|
||||
register: this.generation.padRegisters[0],
|
||||
velocity: 0.052,
|
||||
},
|
||||
{
|
||||
source: { baseMidi: rootMidi, offsets: [intervals[1]] },
|
||||
register: PAD_REGISTERS[1],
|
||||
register: this.generation.padRegisters[1],
|
||||
velocity: 0.041,
|
||||
},
|
||||
{
|
||||
source: { baseMidi: rootMidi, offsets: [intervals[3], intervals[2]] },
|
||||
register: PAD_REGISTERS[2],
|
||||
register: this.generation.padRegisters[2],
|
||||
velocity: 0.033,
|
||||
},
|
||||
];
|
||||
|
|
@ -504,7 +431,7 @@ export class GenerativePianoEngine {
|
|||
expression: number,
|
||||
selectedColorIndex: GardenAudioColorIndex
|
||||
): void {
|
||||
const pool = COLOR_POOLS[selectedColorIndex];
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const chord = this.getChord(profile, barIndex);
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
|
|
@ -539,7 +466,7 @@ export class GenerativePianoEngine {
|
|||
expression: number,
|
||||
selectedColorIndex: GardenAudioColorIndex
|
||||
): void {
|
||||
const pool = COLOR_POOLS[selectedColorIndex];
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const degrees = this.rotate(pool.scaleDegrees, barIndex + selectedColorIndex);
|
||||
const midi = this.chooseMidi(
|
||||
{
|
||||
|
|
@ -573,7 +500,7 @@ export class GenerativePianoEngine {
|
|||
noteCount: number
|
||||
): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const pool = COLOR_POOLS[selectedColorIndex];
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const degrees = this.rotate(pool.scaleDegrees, Math.round(strength * 3));
|
||||
|
||||
for (let index = 0; index < noteCount; index += 1) {
|
||||
|
|
@ -598,8 +525,8 @@ export class GenerativePianoEngine {
|
|||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
startTime:
|
||||
now +
|
||||
this.engineConfig.startDelaySeconds +
|
||||
index * GESTURE_ACCENT_SPACING_SECONDS,
|
||||
this.config.startDelaySeconds +
|
||||
index * this.generation.gestureAccentSpacingSeconds,
|
||||
durationSeconds: 0.48 + strength * 0.22,
|
||||
pan: this.getColorPan(selectedColorIndex),
|
||||
delaySend: 0.012,
|
||||
|
|
@ -626,7 +553,7 @@ export class GenerativePianoEngine {
|
|||
brightnessBias: number;
|
||||
}): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const pool = COLOR_POOLS[selectedColorIndex];
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const register = this.getBiasedRegister(pool, registerBias, 0);
|
||||
const chord = this.getChord(profile, this.getGlobalBarIndex(now));
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
|
|
@ -688,9 +615,9 @@ export class GenerativePianoEngine {
|
|||
maniaAmount: number;
|
||||
}): void {
|
||||
const lifetimeSeconds =
|
||||
BRUSH_LAYER_BASE_SECONDS +
|
||||
strength * BRUSH_LAYER_ENERGY_SECONDS +
|
||||
mirrorAmount * BRUSH_LAYER_MIRROR_SECONDS;
|
||||
this.generation.brushLayerBaseSeconds +
|
||||
strength * this.generation.brushLayerEnergySeconds +
|
||||
mirrorAmount * this.generation.brushLayerMirrorSeconds;
|
||||
|
||||
this.brushPhraseLayers.push({
|
||||
vibe,
|
||||
|
|
@ -713,8 +640,10 @@ export class GenerativePianoEngine {
|
|||
maniaAmount,
|
||||
});
|
||||
|
||||
if (this.brushPhraseLayers.length > MAX_BRUSH_PHRASE_LAYERS) {
|
||||
this.brushPhraseLayers = this.brushPhraseLayers.slice(-MAX_BRUSH_PHRASE_LAYERS);
|
||||
if (this.brushPhraseLayers.length > this.generation.maxBrushPhraseLayers) {
|
||||
this.brushPhraseLayers = this.brushPhraseLayers.slice(
|
||||
-this.generation.maxBrushPhraseLayers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -762,8 +691,8 @@ export class GenerativePianoEngine {
|
|||
layer.motifOffsets.push(
|
||||
this.getMotifOffset({ registerBias, contour, pressureDelta, strength })
|
||||
);
|
||||
if (layer.motifOffsets.length > BRUSH_MOTIF_MAX_STEPS) {
|
||||
layer.motifOffsets = layer.motifOffsets.slice(-BRUSH_MOTIF_MAX_STEPS);
|
||||
if (layer.motifOffsets.length > this.generation.brushMotifMaxSteps) {
|
||||
layer.motifOffsets = layer.motifOffsets.slice(-this.generation.brushMotifMaxSteps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -780,8 +709,8 @@ export class GenerativePianoEngine {
|
|||
activity: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
}): void {
|
||||
const earliestStart = now + this.engineConfig.piano.scheduleAheadSeconds;
|
||||
this.nextBrushStreamAt ??= now + this.engineConfig.startDelaySeconds;
|
||||
const earliestStart = now + this.config.piano.scheduleAheadSeconds;
|
||||
this.nextBrushStreamAt ??= now + this.config.startDelaySeconds;
|
||||
|
||||
this.brushPhraseLayers = this.brushPhraseLayers.filter(
|
||||
(layer) => layer.expiresAt > earliestStart
|
||||
|
|
@ -795,7 +724,7 @@ export class GenerativePianoEngine {
|
|||
|
||||
while (this.nextBrushStreamAt <= lookaheadEnd) {
|
||||
const frame = this.getBrushStreamFrame(this.nextBrushStreamAt, activity);
|
||||
if (frame.intensity >= BRUSH_LAYER_MIN_INTENSITY) {
|
||||
if (frame.intensity >= this.generation.brushLayerMinIntensity) {
|
||||
this.playBrushStreamNote({
|
||||
vibe,
|
||||
startTime: this.nextBrushStreamAt,
|
||||
|
|
@ -823,7 +752,7 @@ export class GenerativePianoEngine {
|
|||
layer: BrushPhraseLayer | null;
|
||||
}): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const pool = COLOR_POOLS[selectedColorIndex];
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const maniaAmount = layer?.maniaAmount ?? clamp01((intensity - 0.82) / 0.18);
|
||||
const register = this.getBiasedRegister(
|
||||
pool,
|
||||
|
|
@ -860,7 +789,10 @@ export class GenerativePianoEngine {
|
|||
0.62
|
||||
);
|
||||
const delaySend = clamp(
|
||||
0.012 + intensity * 0.011 + (layer?.mirrorAmount ?? 0) * 0.004 - maniaAmount * 0.006,
|
||||
0.012 +
|
||||
intensity * 0.011 +
|
||||
(layer?.mirrorAmount ?? 0) * 0.004 -
|
||||
maniaAmount * 0.006,
|
||||
0.006,
|
||||
0.032
|
||||
);
|
||||
|
|
@ -888,7 +820,10 @@ export class GenerativePianoEngine {
|
|||
),
|
||||
});
|
||||
|
||||
if (maniaAmount >= 0.62 && (this.brushStreamNoteIndex % 2 === 1 || intensity >= 0.9)) {
|
||||
if (
|
||||
maniaAmount >= 0.62 &&
|
||||
(this.brushStreamNoteIndex % 2 === 1 || intensity >= 0.9)
|
||||
) {
|
||||
const echoMidi = midi + 12 <= 88 ? midi + 12 : midi - 12;
|
||||
this.playNote({
|
||||
midi: echoMidi,
|
||||
|
|
@ -897,7 +832,7 @@ export class GenerativePianoEngine {
|
|||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
startTime:
|
||||
startTime +
|
||||
BRUSH_MOTIF_CANON_DELAY_SECONDS +
|
||||
this.generation.brushMotifCanonDelaySeconds +
|
||||
(layer?.mirrorAmount ?? 0) * 0.04,
|
||||
durationSeconds: Math.max(0.11, durationSeconds * 0.68),
|
||||
pan: clamp(-pan * 0.75, -1, 1),
|
||||
|
|
@ -948,12 +883,12 @@ export class GenerativePianoEngine {
|
|||
private getBrushStreamIntervalSeconds(intensity: number): number {
|
||||
const intervalBeats =
|
||||
intensity >= 0.85
|
||||
? BRUSH_STREAM_MANIC_INTERVAL_BEATS
|
||||
? this.generation.brushStreamManicIntervalBeats
|
||||
: intensity >= 0.62
|
||||
? BRUSH_STREAM_INTENSE_INTERVAL_BEATS
|
||||
? this.generation.brushStreamIntenseIntervalBeats
|
||||
: intensity >= 0.34
|
||||
? BRUSH_STREAM_ACTIVE_INTERVAL_BEATS
|
||||
: BRUSH_STREAM_IDLE_INTERVAL_BEATS;
|
||||
? this.generation.brushStreamActiveIntervalBeats
|
||||
: this.generation.brushStreamIdleIntervalBeats;
|
||||
return this.getBeatDurationSeconds() * intervalBeats;
|
||||
}
|
||||
|
||||
|
|
@ -1037,7 +972,7 @@ export class GenerativePianoEngine {
|
|||
selectedColorIndex,
|
||||
}: {
|
||||
layer: BrushPhraseLayer | null;
|
||||
pool: ColorPool;
|
||||
pool: GardenAudioColorPool;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
}): Array<number> {
|
||||
const colorOffset = this.config.colorVoices[selectedColorIndex].scaleDegreeOffset;
|
||||
|
|
@ -1063,10 +998,10 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
private getBiasedRegister(
|
||||
register: Register,
|
||||
register: GardenAudioRegister,
|
||||
registerBias: number,
|
||||
maniaAmount: number
|
||||
): Register {
|
||||
): GardenAudioRegister {
|
||||
const shift = Math.round(registerBias * 7 + maniaAmount * 4);
|
||||
const midiMin = clamp(register.midiMin + shift, 36, 86);
|
||||
const midiMax = clamp(register.midiMax + shift, midiMin + 4, 91);
|
||||
|
|
@ -1101,7 +1036,7 @@ export class GenerativePianoEngine {
|
|||
|
||||
private chooseMidi(
|
||||
pitchSource: PitchSource,
|
||||
register: Register,
|
||||
register: GardenAudioRegister,
|
||||
previousMidi: number | null = null,
|
||||
avoidRepeat = false
|
||||
): number {
|
||||
|
|
@ -1122,7 +1057,7 @@ export class GenerativePianoEngine {
|
|||
|
||||
private getCandidates(
|
||||
pitchSource: PitchSource,
|
||||
register: Register
|
||||
register: GardenAudioRegister
|
||||
): Array<PitchCandidate> {
|
||||
const candidates: Array<PitchCandidate> = [];
|
||||
|
||||
|
|
@ -1140,15 +1075,18 @@ export class GenerativePianoEngine {
|
|||
|
||||
private scoreCandidate(
|
||||
candidate: PitchCandidate,
|
||||
register: Register,
|
||||
register: GardenAudioRegister,
|
||||
previousMidi: number,
|
||||
avoidRepeat: boolean
|
||||
): number {
|
||||
return (
|
||||
Math.abs(candidate.midi - previousMidi) +
|
||||
Math.abs(candidate.midi - register.preferredMidi) * NOTE_SCORE_REGISTER_WEIGHT +
|
||||
candidate.preference * NOTE_SCORE_PREFERENCE_WEIGHT +
|
||||
(avoidRepeat && candidate.midi === previousMidi ? NOTE_SCORE_REPEAT_PENALTY : 0)
|
||||
Math.abs(candidate.midi - register.preferredMidi) *
|
||||
this.generation.noteScoreRegisterWeight +
|
||||
candidate.preference * this.generation.noteScorePreferenceWeight +
|
||||
(avoidRepeat && candidate.midi === previousMidi
|
||||
? this.generation.noteScoreRepeatPenalty
|
||||
: 0)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1157,15 +1095,17 @@ export class GenerativePianoEngine {
|
|||
return true;
|
||||
}
|
||||
|
||||
return barIndex % SUPPORT_BAR_SPACING === SUPPORT_BAR_OFFSET;
|
||||
return (
|
||||
barIndex % this.generation.supportBarSpacing === this.generation.supportBarOffset
|
||||
);
|
||||
}
|
||||
|
||||
private shouldPlayTexture(expression: number, barIndex: number): boolean {
|
||||
const spacing =
|
||||
expression < 0.35
|
||||
? IDLE_TEXTURE_BAR_SPACING
|
||||
? this.generation.idleTextureBarSpacing
|
||||
: expression < 0.7
|
||||
? MEDIUM_TEXTURE_BAR_SPACING
|
||||
? this.generation.mediumTextureBarSpacing
|
||||
: 1;
|
||||
|
||||
return barIndex % spacing === (spacing === 1 ? 0 : 1);
|
||||
|
|
@ -1188,7 +1128,7 @@ export class GenerativePianoEngine {
|
|||
|
||||
private getChord(profile: GardenAudioVibeProfile, barIndex: number): GardenAudioChord {
|
||||
const progressionIndex =
|
||||
Math.floor(barIndex / CHORD_BARS) % profile.progression.length;
|
||||
Math.floor(barIndex / this.generation.chordBars) % profile.progression.length;
|
||||
return profile.progression[progressionIndex];
|
||||
}
|
||||
|
||||
|
|
@ -1199,7 +1139,7 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
private getColorPan(selectedColorIndex: GardenAudioColorIndex): number {
|
||||
const pool = COLOR_POOLS[selectedColorIndex];
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const colorVoice = this.config.colorVoices[selectedColorIndex];
|
||||
return clamp(pool.pan + colorVoice.panOffset * 0.35, -1, 1);
|
||||
}
|
||||
|
|
@ -1213,8 +1153,8 @@ export class GenerativePianoEngine {
|
|||
return clamp(
|
||||
this.config.piano.lowpassHz * profile.brightness * (0.58 + expression * 0.32) +
|
||||
midiLift,
|
||||
this.engineConfig.piano.lowpassMinHz,
|
||||
this.engineConfig.piano.lowpassMaxHz
|
||||
this.config.piano.lowpassMinHz,
|
||||
this.config.piano.lowpassMaxHz
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1223,7 +1163,7 @@ export class GenerativePianoEngine {
|
|||
return;
|
||||
}
|
||||
|
||||
const earliestStart = now + this.engineConfig.piano.scheduleAheadSeconds;
|
||||
const earliestStart = now + this.config.piano.scheduleAheadSeconds;
|
||||
if (this.nextBeatAt >= earliestStart) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue