more cleaning up
This commit is contained in:
parent
2c7d72a699
commit
560398fefb
110 changed files with 931 additions and 2645 deletions
|
|
@ -2,9 +2,9 @@ import { clamp, clamp01 } from '../utils/clamp';
|
|||
import { VibePreset } from '../vibes';
|
||||
import {
|
||||
GardenAudioChord,
|
||||
GardenAudioColorPool,
|
||||
GardenAudioConfig,
|
||||
GardenAudioRegister,
|
||||
GardenAudioStylePool,
|
||||
GardenAudioVibeProfile,
|
||||
} from './garden-audio-config';
|
||||
import {
|
||||
|
|
@ -12,13 +12,14 @@ import {
|
|||
getChordIntervals,
|
||||
getVibeProfile,
|
||||
} from './garden-audio-music';
|
||||
import { GardenAudioColorIndex, PianoNote } from './garden-audio-types';
|
||||
import { PianoNote } from './garden-audio-types';
|
||||
|
||||
type GardenAudioStyleIndex = 0 | 1 | 2;
|
||||
|
||||
interface RenderLookaheadRequest {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
activity: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
lookaheadSeconds?: number;
|
||||
}
|
||||
|
||||
|
|
@ -26,14 +27,6 @@ interface StrokeAccentRequest {
|
|||
vibe: VibePreset;
|
||||
now: number;
|
||||
activity: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
mirrorAmount?: number;
|
||||
panBias?: number;
|
||||
registerBias?: number;
|
||||
brightnessBias?: number;
|
||||
contour?: number;
|
||||
pressureAmount?: number;
|
||||
pressureDelta?: number;
|
||||
maniaAmount?: number;
|
||||
}
|
||||
|
||||
|
|
@ -41,14 +34,6 @@ interface TouchDownRequest {
|
|||
vibe: VibePreset;
|
||||
now: number;
|
||||
strength: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
mirrorAmount?: number;
|
||||
panBias?: number;
|
||||
registerBias?: number;
|
||||
brightnessBias?: number;
|
||||
contour?: number;
|
||||
pressureAmount?: number;
|
||||
pressureDelta?: number;
|
||||
maniaAmount?: number;
|
||||
}
|
||||
|
||||
|
|
@ -66,16 +51,9 @@ interface BrushPhraseLayer {
|
|||
vibe: VibePreset;
|
||||
startedAt: number;
|
||||
expiresAt: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
styleIndex: GardenAudioStyleIndex;
|
||||
energy: number;
|
||||
mirrorAmount: number;
|
||||
motifOffsets: Array<number>;
|
||||
panBias: number;
|
||||
registerBias: number;
|
||||
brightnessBias: number;
|
||||
contour: number;
|
||||
pressureAmount: number;
|
||||
pressureDelta: number;
|
||||
maniaAmount: number;
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +64,7 @@ export class GenerativePianoEngine {
|
|||
private isWaitingForGestureAccent = false;
|
||||
private lastGestureAccentAt = Number.NEGATIVE_INFINITY;
|
||||
private lastStrokeAccentAt = Number.NEGATIVE_INFINITY;
|
||||
private readonly lastMidiByColor: [number | null, number | null, number | null] = [
|
||||
private readonly lastMidiByStyle: [number | null, number | null, number | null] = [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
|
@ -130,31 +108,15 @@ export class GenerativePianoEngine {
|
|||
this.isWaitingForGestureAccent = false;
|
||||
}
|
||||
|
||||
public recordTouchDown({
|
||||
private recordTouchDown({
|
||||
vibe,
|
||||
now,
|
||||
strength,
|
||||
selectedColorIndex,
|
||||
mirrorAmount = 0,
|
||||
panBias = 0,
|
||||
registerBias = 0,
|
||||
brightnessBias = 0.5,
|
||||
contour = 0,
|
||||
pressureAmount = 0,
|
||||
pressureDelta = 0,
|
||||
maniaAmount = 0,
|
||||
}: TouchDownRequest): void {
|
||||
const normalizedStrength = clamp01(strength);
|
||||
const normalizedMirrorAmount = clamp01(mirrorAmount);
|
||||
const normalizedMotif = this.normalizeMotif({
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
contour,
|
||||
pressureAmount,
|
||||
pressureDelta,
|
||||
maniaAmount,
|
||||
});
|
||||
const normalizedManiaAmount = clamp01(maniaAmount);
|
||||
const styleIndex = this.getStyleIndex(now);
|
||||
|
||||
this.isWaitingForGestureAccent = false;
|
||||
this.lastGestureAccentAt = now;
|
||||
|
|
@ -163,18 +125,14 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
now,
|
||||
strength: normalizedStrength,
|
||||
selectedColorIndex,
|
||||
mirrorAmount: normalizedMirrorAmount,
|
||||
...normalizedMotif,
|
||||
styleIndex,
|
||||
maniaAmount: normalizedManiaAmount,
|
||||
});
|
||||
this.playTouchNote({
|
||||
vibe,
|
||||
now,
|
||||
selectedColorIndex,
|
||||
styleIndex,
|
||||
strength: normalizedStrength,
|
||||
panBias: normalizedMotif.panBias,
|
||||
registerBias: normalizedMotif.registerBias,
|
||||
brightnessBias: normalizedMotif.brightnessBias,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -182,27 +140,11 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
now,
|
||||
activity,
|
||||
selectedColorIndex,
|
||||
mirrorAmount = 0,
|
||||
panBias = 0,
|
||||
registerBias = 0,
|
||||
brightnessBias = 0.5,
|
||||
contour = 0,
|
||||
pressureAmount = 0,
|
||||
pressureDelta = 0,
|
||||
maniaAmount = 0,
|
||||
}: StrokeAccentRequest): void {
|
||||
const strength = clamp01(activity);
|
||||
const normalizedMirrorAmount = clamp01(mirrorAmount);
|
||||
const normalizedMotif = this.normalizeMotif({
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
contour,
|
||||
pressureAmount,
|
||||
pressureDelta,
|
||||
maniaAmount,
|
||||
});
|
||||
const normalizedManiaAmount = clamp01(maniaAmount);
|
||||
const styleIndex = this.getStyleIndex(now);
|
||||
|
||||
if (
|
||||
this.isWaitingForGestureAccent &&
|
||||
|
|
@ -212,9 +154,7 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
now,
|
||||
strength,
|
||||
selectedColorIndex,
|
||||
mirrorAmount: normalizedMirrorAmount,
|
||||
...normalizedMotif,
|
||||
maniaAmount: normalizedManiaAmount,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -223,16 +163,15 @@ export class GenerativePianoEngine {
|
|||
this.updateBrushPhraseLayer({
|
||||
now,
|
||||
strength,
|
||||
selectedColorIndex,
|
||||
mirrorAmount: normalizedMirrorAmount,
|
||||
...normalizedMotif,
|
||||
styleIndex,
|
||||
maniaAmount: normalizedManiaAmount,
|
||||
});
|
||||
if (
|
||||
strength >= this.generation.strokeAccentThreshold &&
|
||||
now - this.lastStrokeAccentAt >= this.generation.strokeAccentMinIntervalSeconds
|
||||
) {
|
||||
this.lastStrokeAccentAt = now;
|
||||
this.playGestureAccent(vibe, now, selectedColorIndex, strength, 1);
|
||||
this.playGestureAccent(vibe, now, styleIndex, strength);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +179,6 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
now,
|
||||
activity,
|
||||
selectedColorIndex,
|
||||
lookaheadSeconds = this.config.rhythm.lookaheadSeconds,
|
||||
}: RenderLookaheadRequest): void {
|
||||
this.prime(now);
|
||||
|
|
@ -250,7 +188,7 @@ export class GenerativePianoEngine {
|
|||
return;
|
||||
}
|
||||
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const profile = getVibeProfile(vibe);
|
||||
const lookaheadEnd = now + lookaheadSeconds;
|
||||
while (this.nextBeatAt <= lookaheadEnd) {
|
||||
this.renderBeat({
|
||||
|
|
@ -258,7 +196,6 @@ export class GenerativePianoEngine {
|
|||
beatIndex: this.beatIndex,
|
||||
startTime: this.nextBeatAt,
|
||||
expression: this.getExpression(activity),
|
||||
selectedColorIndex,
|
||||
});
|
||||
this.nextBeatAt += this.getBeatDurationSeconds();
|
||||
this.beatIndex += 1;
|
||||
|
|
@ -268,12 +205,11 @@ export class GenerativePianoEngine {
|
|||
now,
|
||||
lookaheadEnd,
|
||||
activity,
|
||||
selectedColorIndex,
|
||||
});
|
||||
}
|
||||
|
||||
public playVibeChangeStinger(vibe: VibePreset, now: number): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const profile = getVibeProfile(vibe);
|
||||
const chord = this.getChord(profile, 0);
|
||||
const intervals = getChordIntervals(chord, true);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
|
|
@ -324,9 +260,9 @@ export class GenerativePianoEngine {
|
|||
this.isWaitingForGestureAccent = false;
|
||||
this.lastGestureAccentAt = Number.NEGATIVE_INFINITY;
|
||||
this.lastStrokeAccentAt = Number.NEGATIVE_INFINITY;
|
||||
this.lastMidiByColor[0] = null;
|
||||
this.lastMidiByColor[1] = null;
|
||||
this.lastMidiByColor[2] = null;
|
||||
this.lastMidiByStyle[0] = null;
|
||||
this.lastMidiByStyle[1] = null;
|
||||
this.lastMidiByStyle[2] = null;
|
||||
this.brushPhraseLayers = [];
|
||||
this.nextBrushStreamAt = null;
|
||||
this.brushStreamNoteIndex = 0;
|
||||
|
|
@ -338,31 +274,30 @@ export class GenerativePianoEngine {
|
|||
beatIndex,
|
||||
startTime,
|
||||
expression,
|
||||
selectedColorIndex,
|
||||
}: {
|
||||
profile: GardenAudioVibeProfile;
|
||||
beatIndex: number;
|
||||
startTime: number;
|
||||
expression: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
}): void {
|
||||
const beatsPerBar = this.getBeatsPerBar();
|
||||
const beatInBar = beatIndex % beatsPerBar;
|
||||
const barIndex = Math.floor(beatIndex / beatsPerBar);
|
||||
const styleIndex = this.getStyleIndex(startTime);
|
||||
|
||||
if (beatInBar === 0 && barIndex % this.generation.chordBars === 0) {
|
||||
this.playPadChord(profile, barIndex, startTime, expression);
|
||||
}
|
||||
|
||||
if (beatInBar === 0 && this.shouldPlaySupport(expression, barIndex)) {
|
||||
this.playSupportNote(profile, barIndex, startTime, expression, selectedColorIndex);
|
||||
this.playSupportNote(profile, barIndex, startTime, expression, styleIndex);
|
||||
}
|
||||
|
||||
if (
|
||||
beatInBar === this.generation.textureBeat &&
|
||||
this.shouldPlayTexture(expression, barIndex)
|
||||
) {
|
||||
this.playTextureNote(profile, barIndex, startTime, expression, selectedColorIndex);
|
||||
this.playTextureNote(profile, barIndex, startTime, expression, styleIndex);
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -374,7 +309,7 @@ export class GenerativePianoEngine {
|
|||
barIndex + 1,
|
||||
startTime,
|
||||
expression * 0.9,
|
||||
selectedColorIndex
|
||||
styleIndex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -429,31 +364,31 @@ export class GenerativePianoEngine {
|
|||
barIndex: number,
|
||||
startTime: number,
|
||||
expression: number,
|
||||
selectedColorIndex: GardenAudioColorIndex
|
||||
styleIndex: GardenAudioStyleIndex
|
||||
): void {
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const chord = this.getChord(profile, barIndex);
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const midi = this.chooseMidi(
|
||||
{
|
||||
baseMidi: rootMidi,
|
||||
offsets: this.getSupportOffsets(chordIntervals, selectedColorIndex),
|
||||
offsets: this.getSupportOffsets(chordIntervals, styleIndex),
|
||||
},
|
||||
pool,
|
||||
this.lastMidiByColor[selectedColorIndex],
|
||||
this.lastMidiByStyle[styleIndex],
|
||||
true
|
||||
);
|
||||
|
||||
this.lastMidiByColor[selectedColorIndex] = midi;
|
||||
this.lastMidiByStyle[styleIndex] = midi;
|
||||
this.playNote({
|
||||
midi,
|
||||
velocity:
|
||||
(0.105 + expression * 0.07) *
|
||||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
this.config.styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds: 1.35 + expression * 0.4,
|
||||
pan: this.getColorPan(selectedColorIndex),
|
||||
pan: this.getStylePan(styleIndex),
|
||||
delaySend: 0.016 + expression * 0.006,
|
||||
lowpassHz: this.getLowpassHz(profile, midi, expression * 0.7),
|
||||
});
|
||||
|
|
@ -464,29 +399,29 @@ export class GenerativePianoEngine {
|
|||
barIndex: number,
|
||||
startTime: number,
|
||||
expression: number,
|
||||
selectedColorIndex: GardenAudioColorIndex
|
||||
styleIndex: GardenAudioStyleIndex
|
||||
): void {
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const degrees = this.rotate(pool.scaleDegrees, barIndex + selectedColorIndex);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const degrees = this.rotate(pool.scaleDegrees, barIndex + styleIndex);
|
||||
const midi = this.chooseMidi(
|
||||
{
|
||||
baseMidi: profile.rootMidi,
|
||||
offsets: degrees.map((degree) => degreeToSemitone(profile, degree)),
|
||||
},
|
||||
pool,
|
||||
this.lastMidiByColor[selectedColorIndex],
|
||||
this.lastMidiByStyle[styleIndex],
|
||||
true
|
||||
);
|
||||
|
||||
this.lastMidiByColor[selectedColorIndex] = midi;
|
||||
this.lastMidiByStyle[styleIndex] = midi;
|
||||
this.playNote({
|
||||
midi,
|
||||
velocity:
|
||||
(0.09 + expression * 0.08) *
|
||||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
this.config.styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds: 0.62 + expression * 0.24,
|
||||
pan: this.getColorPan(selectedColorIndex),
|
||||
pan: this.getStylePan(styleIndex),
|
||||
delaySend: 0.016 + expression * 0.006,
|
||||
lowpassHz: this.getLowpassHz(profile, midi, expression),
|
||||
});
|
||||
|
|
@ -495,95 +430,74 @@ export class GenerativePianoEngine {
|
|||
private playGestureAccent(
|
||||
vibe: VibePreset,
|
||||
now: number,
|
||||
selectedColorIndex: GardenAudioColorIndex,
|
||||
strength: number,
|
||||
noteCount: number
|
||||
styleIndex: GardenAudioStyleIndex,
|
||||
strength: number
|
||||
): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const profile = getVibeProfile(vibe);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const degrees = this.rotate(pool.scaleDegrees, Math.round(strength * 3));
|
||||
|
||||
for (let index = 0; index < noteCount; index += 1) {
|
||||
const midi = this.chooseMidi(
|
||||
{
|
||||
baseMidi: profile.rootMidi,
|
||||
offsets: degrees
|
||||
.slice(index)
|
||||
.concat(degrees.slice(0, index))
|
||||
.map((degree) => degreeToSemitone(profile, degree)),
|
||||
},
|
||||
pool,
|
||||
this.lastMidiByColor[selectedColorIndex],
|
||||
true
|
||||
);
|
||||
const midi = this.chooseMidi(
|
||||
{
|
||||
baseMidi: profile.rootMidi,
|
||||
offsets: degrees.map((degree) => degreeToSemitone(profile, degree)),
|
||||
},
|
||||
pool,
|
||||
this.lastMidiByStyle[styleIndex],
|
||||
true
|
||||
);
|
||||
|
||||
this.lastMidiByColor[selectedColorIndex] = midi;
|
||||
this.playNote({
|
||||
midi,
|
||||
velocity:
|
||||
(0.12 + strength * 0.09) *
|
||||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
startTime:
|
||||
now +
|
||||
this.config.startDelaySeconds +
|
||||
index * this.generation.gestureAccentSpacingSeconds,
|
||||
durationSeconds: 0.48 + strength * 0.22,
|
||||
pan: this.getColorPan(selectedColorIndex),
|
||||
delaySend: 0.012,
|
||||
lowpassHz: this.getLowpassHz(profile, midi, strength),
|
||||
});
|
||||
}
|
||||
this.lastMidiByStyle[styleIndex] = midi;
|
||||
this.playNote({
|
||||
midi,
|
||||
velocity:
|
||||
(0.12 + strength * 0.09) * this.config.styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime: now + this.config.startDelaySeconds,
|
||||
durationSeconds: 0.48 + strength * 0.22,
|
||||
pan: this.getStylePan(styleIndex),
|
||||
delaySend: 0.012,
|
||||
lowpassHz: this.getLowpassHz(profile, midi, strength),
|
||||
});
|
||||
}
|
||||
|
||||
private playTouchNote({
|
||||
vibe,
|
||||
now,
|
||||
selectedColorIndex,
|
||||
styleIndex,
|
||||
strength,
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
}: {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
styleIndex: GardenAudioStyleIndex;
|
||||
strength: number;
|
||||
panBias: number;
|
||||
registerBias: number;
|
||||
brightnessBias: number;
|
||||
}): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const register = this.getBiasedRegister(pool, registerBias, 0);
|
||||
const profile = getVibeProfile(vibe);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const register = this.getBiasedRegister(pool, 0);
|
||||
const chord = this.getChord(profile, this.getGlobalBarIndex(now));
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const midi = this.chooseMidi(
|
||||
{
|
||||
baseMidi: rootMidi,
|
||||
offsets: this.getSupportOffsets(chordIntervals, selectedColorIndex),
|
||||
offsets: this.getSupportOffsets(chordIntervals, styleIndex),
|
||||
},
|
||||
register,
|
||||
this.lastMidiByColor[selectedColorIndex],
|
||||
this.lastMidiByStyle[styleIndex],
|
||||
true
|
||||
);
|
||||
|
||||
this.lastMidiByColor[selectedColorIndex] = midi;
|
||||
this.lastMidiByStyle[styleIndex] = midi;
|
||||
this.lastBrushStreamMidi = midi;
|
||||
this.playNote({
|
||||
midi,
|
||||
velocity:
|
||||
(0.14 + strength * 0.11) *
|
||||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
(0.14 + strength * 0.11) * this.config.styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime: now,
|
||||
durationSeconds: 0.55 + strength * 0.18,
|
||||
pan: this.getLayerPan(selectedColorIndex, panBias, 0, 0),
|
||||
pan: this.getStylePan(styleIndex),
|
||||
delaySend: 0.006,
|
||||
lowpassHz: this.getLowpassHz(
|
||||
profile,
|
||||
midi,
|
||||
clamp01(0.45 + strength * 0.35 + brightnessBias * 0.2)
|
||||
),
|
||||
lowpassHz: this.getLowpassHz(profile, midi, clamp01(0.55 + strength * 0.35)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -591,52 +505,26 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
now,
|
||||
strength,
|
||||
selectedColorIndex,
|
||||
mirrorAmount,
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
contour,
|
||||
pressureAmount,
|
||||
pressureDelta,
|
||||
styleIndex,
|
||||
maniaAmount,
|
||||
}: {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
strength: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
mirrorAmount: number;
|
||||
panBias: number;
|
||||
registerBias: number;
|
||||
brightnessBias: number;
|
||||
contour: number;
|
||||
pressureAmount: number;
|
||||
pressureDelta: number;
|
||||
styleIndex: GardenAudioStyleIndex;
|
||||
maniaAmount: number;
|
||||
}): void {
|
||||
const lifetimeSeconds =
|
||||
this.generation.brushLayerBaseSeconds +
|
||||
strength * this.generation.brushLayerEnergySeconds +
|
||||
mirrorAmount * this.generation.brushLayerMirrorSeconds;
|
||||
strength * this.generation.brushLayerEnergySeconds;
|
||||
|
||||
this.brushPhraseLayers.push({
|
||||
vibe,
|
||||
startedAt: now,
|
||||
expiresAt: now + lifetimeSeconds,
|
||||
selectedColorIndex,
|
||||
styleIndex,
|
||||
energy: strength,
|
||||
mirrorAmount,
|
||||
motifOffsets: this.getInitialMotifOffsets({
|
||||
selectedColorIndex,
|
||||
registerBias,
|
||||
contour,
|
||||
}),
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
contour,
|
||||
pressureAmount,
|
||||
pressureDelta,
|
||||
motifOffsets: [styleIndex - 1],
|
||||
maniaAmount,
|
||||
});
|
||||
|
||||
|
|
@ -650,26 +538,12 @@ export class GenerativePianoEngine {
|
|||
private updateBrushPhraseLayer({
|
||||
now,
|
||||
strength,
|
||||
selectedColorIndex,
|
||||
mirrorAmount,
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
contour,
|
||||
pressureAmount,
|
||||
pressureDelta,
|
||||
styleIndex,
|
||||
maniaAmount,
|
||||
}: {
|
||||
now: number;
|
||||
strength: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
mirrorAmount: number;
|
||||
panBias: number;
|
||||
registerBias: number;
|
||||
brightnessBias: number;
|
||||
contour: number;
|
||||
pressureAmount: number;
|
||||
pressureDelta: number;
|
||||
styleIndex: GardenAudioStyleIndex;
|
||||
maniaAmount: number;
|
||||
}): void {
|
||||
const layer = this.brushPhraseLayers[this.brushPhraseLayers.length - 1];
|
||||
|
|
@ -677,20 +551,10 @@ export class GenerativePianoEngine {
|
|||
return;
|
||||
}
|
||||
|
||||
const followAmount = 0.24 + clamp01(strength) * 0.24;
|
||||
layer.selectedColorIndex = selectedColorIndex;
|
||||
layer.styleIndex = styleIndex;
|
||||
layer.energy = Math.max(layer.energy * 0.94, strength);
|
||||
layer.mirrorAmount = Math.max(layer.mirrorAmount * 0.96, mirrorAmount);
|
||||
layer.panBias = mix(layer.panBias, panBias, followAmount);
|
||||
layer.registerBias = mix(layer.registerBias, registerBias, followAmount);
|
||||
layer.brightnessBias = mix(layer.brightnessBias, brightnessBias, followAmount);
|
||||
layer.contour = mix(layer.contour, contour, followAmount);
|
||||
layer.pressureAmount = mix(layer.pressureAmount, pressureAmount, followAmount);
|
||||
layer.pressureDelta = pressureDelta;
|
||||
layer.maniaAmount = Math.max(layer.maniaAmount * 0.92, maniaAmount);
|
||||
layer.motifOffsets.push(
|
||||
this.getMotifOffset({ registerBias, contour, pressureDelta, strength })
|
||||
);
|
||||
layer.motifOffsets.push(this.getMotifOffset(strength));
|
||||
if (layer.motifOffsets.length > this.generation.brushMotifMaxSteps) {
|
||||
layer.motifOffsets = layer.motifOffsets.slice(-this.generation.brushMotifMaxSteps);
|
||||
}
|
||||
|
|
@ -701,13 +565,11 @@ export class GenerativePianoEngine {
|
|||
now,
|
||||
lookaheadEnd,
|
||||
activity,
|
||||
selectedColorIndex,
|
||||
}: {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
lookaheadEnd: number;
|
||||
activity: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
}): void {
|
||||
const earliestStart = now + this.config.piano.scheduleAheadSeconds;
|
||||
this.nextBrushStreamAt ??= now + this.config.startDelaySeconds;
|
||||
|
|
@ -729,7 +591,7 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
startTime: this.nextBrushStreamAt,
|
||||
intensity: frame.intensity,
|
||||
selectedColorIndex: frame.selectedColorIndex ?? selectedColorIndex,
|
||||
styleIndex: this.getStyleIndex(this.nextBrushStreamAt),
|
||||
layer: frame.layer,
|
||||
});
|
||||
}
|
||||
|
|
@ -742,23 +604,19 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
startTime,
|
||||
intensity,
|
||||
selectedColorIndex,
|
||||
styleIndex,
|
||||
layer,
|
||||
}: {
|
||||
vibe: VibePreset;
|
||||
startTime: number;
|
||||
intensity: number;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
styleIndex: GardenAudioStyleIndex;
|
||||
layer: BrushPhraseLayer | null;
|
||||
}): void {
|
||||
const profile = getVibeProfile(this.config, vibe);
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const profile = getVibeProfile(vibe);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const maniaAmount = layer?.maniaAmount ?? clamp01((intensity - 0.82) / 0.18);
|
||||
const register = this.getBiasedRegister(
|
||||
pool,
|
||||
layer?.registerBias ?? 0,
|
||||
maniaAmount * 0.45
|
||||
);
|
||||
const register = this.getBiasedRegister(pool, maniaAmount * 0.45);
|
||||
const chord = this.getChord(profile, this.getGlobalBarIndex(startTime));
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
|
|
@ -766,44 +624,35 @@ export class GenerativePianoEngine {
|
|||
const source = useChordTone
|
||||
? {
|
||||
baseMidi: rootMidi,
|
||||
offsets: this.getSupportOffsets(chordIntervals, selectedColorIndex),
|
||||
offsets: this.getSupportOffsets(chordIntervals, styleIndex),
|
||||
}
|
||||
: {
|
||||
baseMidi: profile.rootMidi,
|
||||
offsets: this.getBrushMotifDegrees({
|
||||
layer,
|
||||
pool,
|
||||
selectedColorIndex,
|
||||
styleIndex,
|
||||
}).map((degree) => degreeToSemitone(profile, degree)),
|
||||
};
|
||||
const midi = this.chooseMidi(source, register, this.lastBrushStreamMidi, true);
|
||||
const pan = this.getLayerPan(
|
||||
selectedColorIndex,
|
||||
layer?.panBias ?? 0,
|
||||
maniaAmount,
|
||||
layer?.mirrorAmount ?? 0
|
||||
);
|
||||
const pan = this.getStylePan(styleIndex);
|
||||
const durationSeconds = clamp(
|
||||
0.48 + intensity * 0.08 - maniaAmount * 0.34,
|
||||
0.14,
|
||||
0.62
|
||||
);
|
||||
const delaySend = clamp(
|
||||
0.012 +
|
||||
intensity * 0.011 +
|
||||
(layer?.mirrorAmount ?? 0) * 0.004 -
|
||||
maniaAmount * 0.006,
|
||||
0.012 + intensity * 0.011 - maniaAmount * 0.006,
|
||||
0.006,
|
||||
0.032
|
||||
);
|
||||
|
||||
this.lastBrushStreamMidi = midi;
|
||||
this.lastMidiByColor[selectedColorIndex] = midi;
|
||||
this.lastMidiByStyle[styleIndex] = midi;
|
||||
this.playNote({
|
||||
midi,
|
||||
velocity:
|
||||
(0.1 + intensity * 0.13) *
|
||||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
(0.1 + intensity * 0.13) * this.config.styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds,
|
||||
pan,
|
||||
|
|
@ -811,12 +660,7 @@ export class GenerativePianoEngine {
|
|||
lowpassHz: this.getLowpassHz(
|
||||
profile,
|
||||
midi,
|
||||
clamp01(
|
||||
0.32 +
|
||||
intensity * 0.48 +
|
||||
(layer?.brightnessBias ?? 0.5) * 0.14 +
|
||||
maniaAmount * 0.18
|
||||
)
|
||||
clamp01(0.39 + intensity * 0.48 + maniaAmount * 0.18)
|
||||
),
|
||||
});
|
||||
|
||||
|
|
@ -829,11 +673,8 @@ export class GenerativePianoEngine {
|
|||
midi: echoMidi,
|
||||
velocity:
|
||||
(0.045 + intensity * 0.05) *
|
||||
this.config.colorVoices[selectedColorIndex].velocityMultiplier,
|
||||
startTime:
|
||||
startTime +
|
||||
this.generation.brushMotifCanonDelaySeconds +
|
||||
(layer?.mirrorAmount ?? 0) * 0.04,
|
||||
this.config.styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime: startTime + this.generation.brushMotifCanonDelaySeconds,
|
||||
durationSeconds: Math.max(0.11, durationSeconds * 0.68),
|
||||
pan: clamp(-pan * 0.75, -1, 1),
|
||||
delaySend: Math.max(0.006, delaySend * 0.72),
|
||||
|
|
@ -847,7 +688,6 @@ export class GenerativePianoEngine {
|
|||
activity: number
|
||||
): {
|
||||
intensity: number;
|
||||
selectedColorIndex: GardenAudioColorIndex | null;
|
||||
layer: BrushPhraseLayer | null;
|
||||
} {
|
||||
const layerStates = this.brushPhraseLayers.map((layer) => ({
|
||||
|
|
@ -855,7 +695,7 @@ export class GenerativePianoEngine {
|
|||
intensity:
|
||||
layer.energy *
|
||||
this.getBrushPhraseFade(layer, startTime) *
|
||||
(0.8 + layer.mirrorAmount * 0.45 + layer.maniaAmount * 0.42),
|
||||
(0.8 + layer.maniaAmount * 0.42),
|
||||
}));
|
||||
const dominant = layerStates.reduce<{
|
||||
layer: BrushPhraseLayer;
|
||||
|
|
@ -875,7 +715,6 @@ export class GenerativePianoEngine {
|
|||
intensity: clamp01(
|
||||
activity * 0.42 + layeredIntensity + (dominant?.layer.maniaAmount ?? 0) * 0.18
|
||||
),
|
||||
selectedColorIndex: dominant?.layer.selectedColorIndex ?? null,
|
||||
layer: dominant?.layer ?? null,
|
||||
};
|
||||
}
|
||||
|
|
@ -898,98 +737,28 @@ export class GenerativePianoEngine {
|
|||
return clamp01(1 - ageSeconds / Math.max(0.001, lifetimeSeconds));
|
||||
}
|
||||
|
||||
private normalizeMotif({
|
||||
panBias,
|
||||
registerBias,
|
||||
brightnessBias,
|
||||
contour,
|
||||
pressureAmount,
|
||||
pressureDelta,
|
||||
maniaAmount,
|
||||
}: {
|
||||
panBias: number;
|
||||
registerBias: number;
|
||||
brightnessBias: number;
|
||||
contour: number;
|
||||
pressureAmount: number;
|
||||
pressureDelta: number;
|
||||
maniaAmount: number;
|
||||
}): {
|
||||
panBias: number;
|
||||
registerBias: number;
|
||||
brightnessBias: number;
|
||||
contour: number;
|
||||
pressureAmount: number;
|
||||
pressureDelta: number;
|
||||
maniaAmount: number;
|
||||
} {
|
||||
return {
|
||||
panBias: clamp(panBias, -1, 1),
|
||||
registerBias: clamp(registerBias, -1, 1),
|
||||
brightnessBias: clamp01(brightnessBias),
|
||||
contour: clamp(contour, -1, 1),
|
||||
pressureAmount: clamp01(pressureAmount),
|
||||
pressureDelta: clamp(pressureDelta, -1, 1),
|
||||
maniaAmount: clamp01(maniaAmount),
|
||||
};
|
||||
}
|
||||
|
||||
private getInitialMotifOffsets({
|
||||
selectedColorIndex,
|
||||
registerBias,
|
||||
contour,
|
||||
}: {
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
registerBias: number;
|
||||
contour: number;
|
||||
}): Array<number> {
|
||||
const start = selectedColorIndex - 1 + Math.round(registerBias);
|
||||
const motion = contour > 0.2 ? 1 : contour < -0.2 ? -1 : 0;
|
||||
return [start, start + motion, start + motion * 2, start + motion];
|
||||
}
|
||||
|
||||
private getMotifOffset({
|
||||
registerBias,
|
||||
contour,
|
||||
pressureDelta,
|
||||
strength,
|
||||
}: {
|
||||
registerBias: number;
|
||||
contour: number;
|
||||
pressureDelta: number;
|
||||
strength: number;
|
||||
}): number {
|
||||
const contourStep = contour > 0.3 ? 1 : contour < -0.3 ? -1 : 0;
|
||||
const registerStep = Math.round(registerBias * 2);
|
||||
const pressureStep = pressureDelta > 0.08 ? 1 : pressureDelta < -0.08 ? -1 : 0;
|
||||
private getMotifOffset(strength: number): number {
|
||||
const energyStep = strength >= 0.82 ? 1 : strength >= 0.55 ? 0 : -1;
|
||||
return clamp(contourStep + registerStep + pressureStep + energyStep, -3, 4);
|
||||
return clamp(energyStep, -3, 4);
|
||||
}
|
||||
|
||||
private getBrushMotifDegrees({
|
||||
layer,
|
||||
pool,
|
||||
selectedColorIndex,
|
||||
styleIndex,
|
||||
}: {
|
||||
layer: BrushPhraseLayer | null;
|
||||
pool: GardenAudioColorPool;
|
||||
selectedColorIndex: GardenAudioColorIndex;
|
||||
pool: GardenAudioStylePool;
|
||||
styleIndex: GardenAudioStyleIndex;
|
||||
}): Array<number> {
|
||||
const colorOffset = this.config.colorVoices[selectedColorIndex].scaleDegreeOffset;
|
||||
const styleOffset = this.config.styleVoices[styleIndex].scaleDegreeOffset;
|
||||
if (!layer || layer.motifOffsets.length === 0) {
|
||||
return this.rotate(pool.scaleDegrees, this.brushStreamNoteIndex + colorOffset);
|
||||
return this.rotate(pool.scaleDegrees, this.brushStreamNoteIndex + styleOffset);
|
||||
}
|
||||
|
||||
const motifOffset =
|
||||
layer.motifOffsets[this.brushStreamNoteIndex % layer.motifOffsets.length];
|
||||
const contourOffset =
|
||||
layer.contour > 0.28
|
||||
? this.brushStreamNoteIndex % 3
|
||||
: layer.contour < -0.28
|
||||
? -(this.brushStreamNoteIndex % 3)
|
||||
: 0;
|
||||
const pressureLift = layer.pressureAmount > 0.68 ? 1 : 0;
|
||||
const baseOffset = colorOffset + motifOffset + contourOffset + pressureLift;
|
||||
const baseOffset = styleOffset + motifOffset;
|
||||
|
||||
return this.rotate(
|
||||
pool.scaleDegrees.map((degree) => degree + baseOffset),
|
||||
|
|
@ -999,10 +768,9 @@ export class GenerativePianoEngine {
|
|||
|
||||
private getBiasedRegister(
|
||||
register: GardenAudioRegister,
|
||||
registerBias: number,
|
||||
maniaAmount: number
|
||||
): GardenAudioRegister {
|
||||
const shift = Math.round(registerBias * 7 + maniaAmount * 4);
|
||||
const shift = Math.round(maniaAmount * 4);
|
||||
const midiMin = clamp(register.midiMin + shift, 36, 86);
|
||||
const midiMax = clamp(register.midiMax + shift, midiMin + 4, 91);
|
||||
|
||||
|
|
@ -1014,26 +782,6 @@ export class GenerativePianoEngine {
|
|||
};
|
||||
}
|
||||
|
||||
private getLayerPan(
|
||||
selectedColorIndex: GardenAudioColorIndex,
|
||||
panBias: number,
|
||||
maniaAmount: number,
|
||||
mirrorAmount: number
|
||||
): number {
|
||||
const shimmer =
|
||||
maniaAmount > 0.4
|
||||
? Math.sin(this.brushStreamNoteIndex * Math.PI * 0.5) * mirrorAmount * 0.14
|
||||
: 0;
|
||||
|
||||
return clamp(
|
||||
this.getColorPan(selectedColorIndex) +
|
||||
panBias * (0.18 + maniaAmount * 0.42) +
|
||||
shimmer,
|
||||
-1,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
private chooseMidi(
|
||||
pitchSource: PitchSource,
|
||||
register: GardenAudioRegister,
|
||||
|
|
@ -1113,13 +861,13 @@ export class GenerativePianoEngine {
|
|||
|
||||
private getSupportOffsets(
|
||||
chordIntervals: ReadonlyArray<number>,
|
||||
selectedColorIndex: GardenAudioColorIndex
|
||||
styleIndex: GardenAudioStyleIndex
|
||||
): Array<number> {
|
||||
if (selectedColorIndex === 0) {
|
||||
if (styleIndex === 0) {
|
||||
return [0, chordIntervals[2], 12];
|
||||
}
|
||||
|
||||
if (selectedColorIndex === 1) {
|
||||
if (styleIndex === 1) {
|
||||
return [chordIntervals[1], chordIntervals[2], 0, 12];
|
||||
}
|
||||
|
||||
|
|
@ -1138,10 +886,19 @@ export class GenerativePianoEngine {
|
|||
return Math.floor(elapsedSeconds / this.getBarDurationSeconds());
|
||||
}
|
||||
|
||||
private getColorPan(selectedColorIndex: GardenAudioColorIndex): number {
|
||||
const pool = this.generation.colorPools[selectedColorIndex];
|
||||
const colorVoice = this.config.colorVoices[selectedColorIndex];
|
||||
return clamp(pool.pan + colorVoice.panOffset * 0.35, -1, 1);
|
||||
private getStyleIndex(startTime: number): GardenAudioStyleIndex {
|
||||
const timelineStartedAt = this.timelineStartedAt ?? startTime;
|
||||
const elapsedSeconds = Math.max(0, startTime - timelineStartedAt);
|
||||
const styleCount = this.generation.stylePools.length;
|
||||
const rotationSeconds = Math.max(0.001, this.generation.styleRotationSeconds);
|
||||
return (Math.floor(elapsedSeconds / rotationSeconds) %
|
||||
styleCount) as GardenAudioStyleIndex;
|
||||
}
|
||||
|
||||
private getStylePan(styleIndex: GardenAudioStyleIndex): number {
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const styleVoice = this.config.styleVoices[styleIndex];
|
||||
return clamp(pool.pan + styleVoice.panOffset * 0.35, -1, 1);
|
||||
}
|
||||
|
||||
private getLowpassHz(
|
||||
|
|
@ -1199,6 +956,3 @@ export class GenerativePianoEngine {
|
|||
return values.map((_, index) => values[(index + offset) % values.length]);
|
||||
}
|
||||
}
|
||||
|
||||
const mix = (from: number, to: number, amount: number): number =>
|
||||
from + (to - from) * clamp01(amount);
|
||||
|
|
|
|||
Loading…
Reference in a new issue