simplify more
This commit is contained in:
parent
f03da42b5e
commit
2fe3c69963
40 changed files with 690 additions and 873 deletions
|
|
@ -1,11 +1,9 @@
|
|||
import { DEFAULT_AUDIO_VOLUME } from '../app-constants';
|
||||
import type { PianoNoteRole } from './garden-audio-types';
|
||||
|
||||
type GardenAudioChordQuality = 'major' | 'minor';
|
||||
|
||||
export interface GardenAudioChord {
|
||||
rootOffset: number;
|
||||
quality: GardenAudioChordQuality;
|
||||
quality: 'major' | 'minor';
|
||||
}
|
||||
|
||||
export interface GardenAudioVibeSettings {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,6 @@ export interface GardenAudioStroke {
|
|||
elapsedSeconds: number;
|
||||
}
|
||||
|
||||
export interface GardenAudioStartOptions {
|
||||
userGesture?: boolean;
|
||||
}
|
||||
|
||||
export interface LoadedPianoSample {
|
||||
midi: number;
|
||||
buffer: AudioBuffer;
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ import { GardenAudioGestureState } from './garden-audio-gesture-state';
|
|||
import { GardenAudioGraph } from './garden-audio-graph';
|
||||
import { getStrokeMetrics } from './garden-audio-input';
|
||||
import { getVibeProfile } from './garden-audio-music';
|
||||
import type {
|
||||
GardenAudioSnapshot,
|
||||
GardenAudioStartOptions,
|
||||
GardenAudioStroke,
|
||||
} from './garden-audio-types';
|
||||
import type { GardenAudioSnapshot, GardenAudioStroke } from './garden-audio-types';
|
||||
import { GenerativePianoEngine } from './generative-piano';
|
||||
import { NoiseBurstPlayer } from './noise-burst-player';
|
||||
import { PianoSampler } from './piano-sampler';
|
||||
|
|
@ -53,7 +49,7 @@ export class GardenAudio {
|
|||
this.pianoEngine = new GenerativePianoEngine(config, (note) => this.piano.play(note));
|
||||
}
|
||||
|
||||
public start(vibe: VibePreset, options: GardenAudioStartOptions = {}): void {
|
||||
public start(vibe: VibePreset, options: { userGesture?: boolean } = {}): void {
|
||||
const isUserGesture = options.userGesture === true;
|
||||
|
||||
if (this.lifecycle === 'destroyed') {
|
||||
|
|
@ -134,7 +130,7 @@ export class GardenAudio {
|
|||
}
|
||||
}
|
||||
|
||||
public changeVibe(vibe: VibePreset, options: GardenAudioStartOptions = {}): void {
|
||||
public changeVibe(vibe: VibePreset, options: { userGesture?: boolean } = {}): void {
|
||||
const previousVibeId = this.currentVibeId;
|
||||
this.start(vibe, options);
|
||||
const didChangeVibe = previousVibeId !== null && previousVibeId !== vibe.id;
|
||||
|
|
|
|||
|
|
@ -46,20 +46,6 @@ const degreeToSemitone = (profile: GardenAudioVibeProfile, degree: number): numb
|
|||
|
||||
type GardenAudioStyleIndex = 0 | 1 | 2;
|
||||
|
||||
interface RenderLookaheadRequest {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
activity: number;
|
||||
lookaheadSeconds?: number;
|
||||
}
|
||||
|
||||
interface StrokeAccentRequest {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
activity: number;
|
||||
maniaAmount?: number;
|
||||
}
|
||||
|
||||
interface TouchDownRequest {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
|
|
@ -118,10 +104,6 @@ export class GenerativePianoEngine {
|
|||
private readonly playNote: (note: PianoNote) => void
|
||||
) {}
|
||||
|
||||
private get generation(): typeof generativePianoTuning {
|
||||
return generativePianoTuning;
|
||||
}
|
||||
|
||||
public prime(now: number, profile: GardenAudioVibeProfile): void {
|
||||
this.activeProfile = profile;
|
||||
this.timelineStartedAt ??= now;
|
||||
|
|
@ -162,7 +144,7 @@ export class GenerativePianoEngine {
|
|||
this.brushPhraseLayers = [];
|
||||
this.brushStreamNoteCountsByBar.clear();
|
||||
|
||||
return releaseStart + this.generation.releaseResolution.fadeAfterSeconds;
|
||||
return releaseStart + generativePianoTuning.releaseResolution.fadeAfterSeconds;
|
||||
}
|
||||
|
||||
private recordTouchDown({
|
||||
|
|
@ -198,7 +180,12 @@ export class GenerativePianoEngine {
|
|||
now,
|
||||
activity,
|
||||
maniaAmount = 0,
|
||||
}: StrokeAccentRequest): void {
|
||||
}: {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
activity: number;
|
||||
maniaAmount?: number;
|
||||
}): void {
|
||||
const profile = getVibeProfile(vibe);
|
||||
this.prime(now, profile);
|
||||
const strength = clamp01(activity);
|
||||
|
|
@ -206,12 +193,12 @@ export class GenerativePianoEngine {
|
|||
const styleIndex = this.getStyleIndex(now);
|
||||
const accentStep = this.getNextStepIndexAt(
|
||||
now,
|
||||
this.generation.gestureAccent.quantizeStepLookahead
|
||||
generativePianoTuning.gestureAccent.quantizeStepLookahead
|
||||
);
|
||||
|
||||
if (
|
||||
this.isWaitingForGestureAccent &&
|
||||
now - this.lastGestureAccentAt >= this.generation.gestureAccentMinIntervalSeconds
|
||||
now - this.lastGestureAccentAt >= generativePianoTuning.gestureAccentMinIntervalSeconds
|
||||
) {
|
||||
this.recordTouchDown({
|
||||
vibe,
|
||||
|
|
@ -230,8 +217,8 @@ export class GenerativePianoEngine {
|
|||
maniaAmount: normalizedManiaAmount,
|
||||
});
|
||||
if (
|
||||
strength >= this.generation.strokeAccentThreshold &&
|
||||
accentStep - this.lastStrokeAccentStep >= this.generation.strokeAccentMinSteps
|
||||
strength >= generativePianoTuning.strokeAccentThreshold &&
|
||||
accentStep - this.lastStrokeAccentStep >= generativePianoTuning.strokeAccentMinSteps
|
||||
) {
|
||||
this.lastStrokeAccentStep = accentStep;
|
||||
this.playGestureAccent(vibe, accentStep, styleIndex, strength);
|
||||
|
|
@ -243,7 +230,12 @@ export class GenerativePianoEngine {
|
|||
now,
|
||||
activity,
|
||||
lookaheadSeconds = GENERATIVE_LOOKAHEAD_SECONDS,
|
||||
}: RenderLookaheadRequest): void {
|
||||
}: {
|
||||
vibe: VibePreset;
|
||||
now: number;
|
||||
activity: number;
|
||||
lookaheadSeconds?: number;
|
||||
}): void {
|
||||
const profile = getVibeProfile(vibe);
|
||||
this.prime(now, profile);
|
||||
this.skipLateBeats(now);
|
||||
|
|
@ -253,13 +245,14 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
const lookaheadEnd = now + lookaheadSeconds;
|
||||
const expression = this.getExpression(activity);
|
||||
while (this.getTimeForStep(this.nextBeatStep) <= lookaheadEnd) {
|
||||
const beatIndex = this.getBeatIndexForStep(this.nextBeatStep);
|
||||
this.renderBeat({
|
||||
profile,
|
||||
beatIndex,
|
||||
startTime: this.getTimeForStep(this.nextBeatStep),
|
||||
expression: this.getExpression(activity),
|
||||
expression,
|
||||
});
|
||||
this.nextBeatStep += this.config.rhythm.stepsPerBeat;
|
||||
}
|
||||
|
|
@ -267,7 +260,7 @@ export class GenerativePianoEngine {
|
|||
vibe,
|
||||
now,
|
||||
lookaheadEnd,
|
||||
activity,
|
||||
activity: expression,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +269,7 @@ export class GenerativePianoEngine {
|
|||
const chord = this.getChord(profile, 0);
|
||||
const intervals = getChordIntervals(chord, true);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const stinger = this.generation.vibeChangeStinger;
|
||||
const stinger = generativePianoTuning.vibeChangeStinger;
|
||||
const offsetsByVoice: ReadonlyArray<ReadonlyArray<number>> = [
|
||||
[0],
|
||||
[intervals[1], intervals[2]],
|
||||
|
|
@ -286,7 +279,7 @@ export class GenerativePianoEngine {
|
|||
offsetsByVoice.forEach((offsets, index) => {
|
||||
const midi = this.chooseMidi(
|
||||
{ baseMidi: rootMidi, offsets },
|
||||
this.generation.padRegisters[index]
|
||||
generativePianoTuning.padRegisters[index]
|
||||
);
|
||||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
|
|
@ -340,7 +333,7 @@ export class GenerativePianoEngine {
|
|||
const barIndex = Math.floor(beatIndex / beatsPerBar);
|
||||
const styleIndex = this.getStyleIndex(startTime);
|
||||
|
||||
if (beatInBar === 0 && barIndex % this.generation.chordBars === 0) {
|
||||
if (beatInBar === 0 && barIndex % generativePianoTuning.chordBars === 0) {
|
||||
this.playPadChord(profile, barIndex, startTime, expression);
|
||||
}
|
||||
|
||||
|
|
@ -349,21 +342,21 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
if (
|
||||
beatInBar === this.generation.textureBeat &&
|
||||
beatInBar === generativePianoTuning.textureBeat &&
|
||||
this.shouldPlayTexture(expression, barIndex)
|
||||
) {
|
||||
this.playTextureNote(profile, barIndex, startTime, expression, styleIndex);
|
||||
}
|
||||
|
||||
if (
|
||||
beatInBar === this.generation.highActivityExtraBeat &&
|
||||
expression >= this.generation.highActivityExtraThreshold
|
||||
beatInBar === generativePianoTuning.highActivityExtraBeat &&
|
||||
expression >= generativePianoTuning.highActivityExtraThreshold
|
||||
) {
|
||||
this.playTextureNote(
|
||||
profile,
|
||||
barIndex + this.generation.highActivityExtra.barOffset,
|
||||
barIndex + generativePianoTuning.highActivityExtra.barOffset,
|
||||
startTime,
|
||||
expression * this.generation.highActivityExtra.expressionMultiplier,
|
||||
expression * generativePianoTuning.highActivityExtra.expressionMultiplier,
|
||||
styleIndex
|
||||
);
|
||||
}
|
||||
|
|
@ -380,23 +373,23 @@ export class GenerativePianoEngine {
|
|||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const durationSeconds =
|
||||
this.getBarDurationSeconds() *
|
||||
this.generation.chordBars *
|
||||
this.generation.padDurationBarScale;
|
||||
generativePianoTuning.chordBars *
|
||||
generativePianoTuning.padDurationBarScale;
|
||||
const notes = [
|
||||
{
|
||||
source: { baseMidi: rootMidi, offsets: [0] },
|
||||
register: this.generation.padRegisters[0],
|
||||
velocity: this.generation.padChord.velocities[0],
|
||||
register: generativePianoTuning.padRegisters[0],
|
||||
velocity: generativePianoTuning.padChord.velocities[0],
|
||||
},
|
||||
{
|
||||
source: { baseMidi: rootMidi, offsets: [intervals[1]] },
|
||||
register: this.generation.padRegisters[1],
|
||||
velocity: this.generation.padChord.velocities[1],
|
||||
register: generativePianoTuning.padRegisters[1],
|
||||
velocity: generativePianoTuning.padChord.velocities[1],
|
||||
},
|
||||
{
|
||||
source: { baseMidi: rootMidi, offsets: [intervals[3], intervals[2]] },
|
||||
register: this.generation.padRegisters[2],
|
||||
velocity: this.generation.padChord.velocities[2],
|
||||
register: generativePianoTuning.padRegisters[2],
|
||||
velocity: generativePianoTuning.padChord.velocities[2],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -411,16 +404,16 @@ export class GenerativePianoEngine {
|
|||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
velocity:
|
||||
velocity + expression * this.generation.padChord.expressionVelocityWeight,
|
||||
velocity + expression * generativePianoTuning.padChord.expressionVelocityWeight,
|
||||
startTime,
|
||||
durationSeconds,
|
||||
pan: register.pan,
|
||||
role: 'pad',
|
||||
delaySend: this.generation.padChord.delaySend,
|
||||
delaySend: generativePianoTuning.padChord.delaySend,
|
||||
lowpassHz: this.getLowpassHz(
|
||||
profile,
|
||||
midi,
|
||||
expression * this.generation.padChord.lowpassExpressionWeight
|
||||
expression * generativePianoTuning.padChord.lowpassExpressionWeight
|
||||
),
|
||||
});
|
||||
});
|
||||
|
|
@ -434,7 +427,7 @@ export class GenerativePianoEngine {
|
|||
const chord = this.getChord(profile, this.getBarIndexForStep(stepIndex));
|
||||
const intervals = getChordIntervals(chord, true);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const release = this.generation.releaseResolution;
|
||||
const release = generativePianoTuning.releaseResolution;
|
||||
const offsetsByVoice: ReadonlyArray<ReadonlyArray<number>> = [
|
||||
[0],
|
||||
[intervals[1], intervals[2]],
|
||||
|
|
@ -442,7 +435,7 @@ export class GenerativePianoEngine {
|
|||
];
|
||||
|
||||
offsetsByVoice.forEach((offsets, index) => {
|
||||
const register = this.generation.padRegisters[index];
|
||||
const register = generativePianoTuning.padRegisters[index];
|
||||
const midi = this.chooseMidi(
|
||||
{ baseMidi: rootMidi, offsets },
|
||||
register,
|
||||
|
|
@ -469,7 +462,7 @@ export class GenerativePianoEngine {
|
|||
expression: number,
|
||||
styleIndex: GardenAudioStyleIndex
|
||||
): void {
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const pool = generativePianoTuning.stylePools[styleIndex];
|
||||
const chord = this.getChord(profile, barIndex);
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
|
|
@ -488,22 +481,22 @@ export class GenerativePianoEngine {
|
|||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
velocity:
|
||||
(this.generation.supportNote.velocityBase +
|
||||
expression * this.generation.supportNote.velocityExpressionWeight) *
|
||||
(generativePianoTuning.supportNote.velocityBase +
|
||||
expression * generativePianoTuning.supportNote.velocityExpressionWeight) *
|
||||
styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds:
|
||||
this.generation.supportNote.durationBaseSeconds +
|
||||
expression * this.generation.supportNote.durationExpressionSeconds,
|
||||
generativePianoTuning.supportNote.durationBaseSeconds +
|
||||
expression * generativePianoTuning.supportNote.durationExpressionSeconds,
|
||||
pan: this.getStylePan(styleIndex),
|
||||
role: 'support',
|
||||
delaySend:
|
||||
this.generation.supportNote.delaySendBase +
|
||||
expression * this.generation.supportNote.delaySendExpressionWeight,
|
||||
generativePianoTuning.supportNote.delaySendBase +
|
||||
expression * generativePianoTuning.supportNote.delaySendExpressionWeight,
|
||||
lowpassHz: this.getLowpassHz(
|
||||
profile,
|
||||
midi,
|
||||
expression * this.generation.supportNote.lowpassExpressionWeight
|
||||
expression * generativePianoTuning.supportNote.lowpassExpressionWeight
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
@ -515,7 +508,7 @@ export class GenerativePianoEngine {
|
|||
expression: number,
|
||||
styleIndex: GardenAudioStyleIndex
|
||||
): void {
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const pool = generativePianoTuning.stylePools[styleIndex];
|
||||
const chord = this.getChord(profile, barIndex);
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const degrees = this.rotate(pool.scaleDegrees, barIndex + styleIndex);
|
||||
|
|
@ -534,18 +527,18 @@ export class GenerativePianoEngine {
|
|||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
velocity:
|
||||
(this.generation.textureNote.velocityBase +
|
||||
expression * this.generation.textureNote.velocityExpressionWeight) *
|
||||
(generativePianoTuning.textureNote.velocityBase +
|
||||
expression * generativePianoTuning.textureNote.velocityExpressionWeight) *
|
||||
styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds:
|
||||
this.generation.textureNote.durationBaseSeconds +
|
||||
expression * this.generation.textureNote.durationExpressionSeconds,
|
||||
generativePianoTuning.textureNote.durationBaseSeconds +
|
||||
expression * generativePianoTuning.textureNote.durationExpressionSeconds,
|
||||
pan: this.getStylePan(styleIndex),
|
||||
role: 'texture',
|
||||
delaySend:
|
||||
this.generation.textureNote.delaySendBase +
|
||||
expression * this.generation.textureNote.delaySendExpressionWeight,
|
||||
generativePianoTuning.textureNote.delaySendBase +
|
||||
expression * generativePianoTuning.textureNote.delaySendExpressionWeight,
|
||||
lowpassHz: this.getLowpassHz(profile, midi, expression),
|
||||
});
|
||||
}
|
||||
|
|
@ -557,13 +550,13 @@ export class GenerativePianoEngine {
|
|||
strength: number
|
||||
): void {
|
||||
const profile = getVibeProfile(vibe);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const pool = generativePianoTuning.stylePools[styleIndex];
|
||||
const startTime = this.getTimeForStep(stepIndex);
|
||||
const chord = this.getChord(profile, this.getBarIndexForStep(stepIndex));
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const degrees = this.rotate(
|
||||
pool.scaleDegrees,
|
||||
Math.round(strength * this.generation.gestureAccent.rotationStrengthMultiplier)
|
||||
Math.round(strength * generativePianoTuning.gestureAccent.rotationStrengthMultiplier)
|
||||
);
|
||||
|
||||
const midi = this.chooseMidi(
|
||||
|
|
@ -581,16 +574,16 @@ export class GenerativePianoEngine {
|
|||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
velocity:
|
||||
(this.generation.gestureAccent.velocityBase +
|
||||
strength * this.generation.gestureAccent.velocityStrengthWeight) *
|
||||
(generativePianoTuning.gestureAccent.velocityBase +
|
||||
strength * generativePianoTuning.gestureAccent.velocityStrengthWeight) *
|
||||
styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds:
|
||||
this.generation.gestureAccent.durationBaseSeconds +
|
||||
strength * this.generation.gestureAccent.durationStrengthSeconds,
|
||||
generativePianoTuning.gestureAccent.durationBaseSeconds +
|
||||
strength * generativePianoTuning.gestureAccent.durationStrengthSeconds,
|
||||
pan: this.getStylePan(styleIndex),
|
||||
role: 'gesture',
|
||||
delaySend: this.generation.gestureAccent.delaySend,
|
||||
delaySend: generativePianoTuning.gestureAccent.delaySend,
|
||||
lowpassHz: this.getLowpassHz(profile, midi, strength),
|
||||
});
|
||||
}
|
||||
|
|
@ -607,7 +600,7 @@ export class GenerativePianoEngine {
|
|||
strength: number;
|
||||
}): void {
|
||||
const profile = getVibeProfile(vibe);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const pool = generativePianoTuning.stylePools[styleIndex];
|
||||
const chord = this.getChord(profile, this.getGlobalBarIndex(now));
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
|
|
@ -626,22 +619,22 @@ export class GenerativePianoEngine {
|
|||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
velocity:
|
||||
(this.generation.touchNote.velocityBase +
|
||||
strength * this.generation.touchNote.velocityStrengthWeight) *
|
||||
(generativePianoTuning.touchNote.velocityBase +
|
||||
strength * generativePianoTuning.touchNote.velocityStrengthWeight) *
|
||||
styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime: now,
|
||||
durationSeconds:
|
||||
this.generation.touchNote.durationBaseSeconds +
|
||||
strength * this.generation.touchNote.durationStrengthSeconds,
|
||||
generativePianoTuning.touchNote.durationBaseSeconds +
|
||||
strength * generativePianoTuning.touchNote.durationStrengthSeconds,
|
||||
pan: this.getStylePan(styleIndex),
|
||||
role: 'gesture',
|
||||
delaySend: this.generation.touchNote.delaySend,
|
||||
delaySend: generativePianoTuning.touchNote.delaySend,
|
||||
lowpassHz: this.getLowpassHz(
|
||||
profile,
|
||||
midi,
|
||||
clamp01(
|
||||
this.generation.touchNote.lowpassBaseExpression +
|
||||
strength * this.generation.touchNote.lowpassStrengthWeight
|
||||
generativePianoTuning.touchNote.lowpassBaseExpression +
|
||||
strength * generativePianoTuning.touchNote.lowpassStrengthWeight
|
||||
)
|
||||
),
|
||||
});
|
||||
|
|
@ -661,8 +654,8 @@ export class GenerativePianoEngine {
|
|||
maniaAmount: number;
|
||||
}): void {
|
||||
const lifetimeSeconds =
|
||||
this.generation.brushLayerBaseSeconds +
|
||||
strength * this.generation.brushLayerEnergySeconds;
|
||||
generativePianoTuning.brushLayerBaseSeconds +
|
||||
strength * generativePianoTuning.brushLayerEnergySeconds;
|
||||
const expiresAt = this.getNextBarTimeAt(now + lifetimeSeconds);
|
||||
|
||||
this.brushPhraseLayers.push({
|
||||
|
|
@ -672,13 +665,13 @@ export class GenerativePianoEngine {
|
|||
expiresAt,
|
||||
styleIndex,
|
||||
energy: strength,
|
||||
motifOffsets: [styleIndex + this.generation.brushPhrase.initialMotifOffset],
|
||||
motifOffsets: [styleIndex + generativePianoTuning.brushPhrase.initialMotifOffset],
|
||||
maniaAmount,
|
||||
});
|
||||
|
||||
if (this.brushPhraseLayers.length > this.generation.maxBrushPhraseLayers) {
|
||||
if (this.brushPhraseLayers.length > generativePianoTuning.maxBrushPhraseLayers) {
|
||||
this.brushPhraseLayers = this.brushPhraseLayers.slice(
|
||||
-this.generation.maxBrushPhraseLayers
|
||||
-generativePianoTuning.maxBrushPhraseLayers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -704,17 +697,17 @@ export class GenerativePianoEngine {
|
|||
layer.styleIndex = styleIndex;
|
||||
layer.energy = Math.max(
|
||||
layer.energy *
|
||||
Math.exp(-elapsedSeconds / this.generation.brushPhrase.energyDecaySeconds),
|
||||
Math.exp(-elapsedSeconds / generativePianoTuning.brushPhrase.energyDecaySeconds),
|
||||
strength
|
||||
);
|
||||
layer.maniaAmount = Math.max(
|
||||
layer.maniaAmount *
|
||||
Math.exp(-elapsedSeconds / this.generation.brushPhrase.maniaDecaySeconds),
|
||||
Math.exp(-elapsedSeconds / generativePianoTuning.brushPhrase.maniaDecaySeconds),
|
||||
maniaAmount
|
||||
);
|
||||
layer.motifOffsets.push(this.getMotifOffset(strength));
|
||||
if (layer.motifOffsets.length > this.generation.brushMotifMaxSteps) {
|
||||
layer.motifOffsets = layer.motifOffsets.slice(-this.generation.brushMotifMaxSteps);
|
||||
if (layer.motifOffsets.length > generativePianoTuning.brushMotifMaxSteps) {
|
||||
layer.motifOffsets = layer.motifOffsets.slice(-generativePianoTuning.brushMotifMaxSteps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -750,7 +743,7 @@ export class GenerativePianoEngine {
|
|||
const startTime = this.getTimeForStep(this.nextBrushStreamStep);
|
||||
const frame = this.getBrushStreamFrame(startTime, activity);
|
||||
if (
|
||||
frame.intensity >= this.generation.brushLayerMinIntensity &&
|
||||
frame.intensity >= generativePianoTuning.brushLayerMinIntensity &&
|
||||
this.reserveBrushStreamNote(this.nextBrushStreamStep)
|
||||
) {
|
||||
this.playBrushStreamNote({
|
||||
|
|
@ -783,22 +776,22 @@ export class GenerativePianoEngine {
|
|||
layer: BrushPhraseLayer | null;
|
||||
}): void {
|
||||
const profile = getVibeProfile(vibe);
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const pool = generativePianoTuning.stylePools[styleIndex];
|
||||
const maniaAmount =
|
||||
layer?.maniaAmount ??
|
||||
clamp01(
|
||||
(intensity - this.generation.brushStream.inferredManiaThreshold) /
|
||||
this.generation.brushStream.inferredManiaRange
|
||||
(intensity - generativePianoTuning.brushStream.inferredManiaThreshold) /
|
||||
generativePianoTuning.brushStream.inferredManiaRange
|
||||
);
|
||||
const register = this.getBiasedRegister(
|
||||
pool,
|
||||
maniaAmount * this.generation.brushStream.registerManiaShift
|
||||
maniaAmount * generativePianoTuning.brushStream.registerManiaShift
|
||||
);
|
||||
const chord = this.getChord(profile, this.getBarIndexForStep(stepIndex));
|
||||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const useChordTone =
|
||||
this.brushStreamNoteIndex % this.generation.brushStream.chordToneEverySteps === 0;
|
||||
this.brushStreamNoteIndex % generativePianoTuning.brushStream.chordToneEverySteps === 0;
|
||||
const source = useChordTone
|
||||
? {
|
||||
baseMidi: rootMidi,
|
||||
|
|
@ -817,18 +810,18 @@ export class GenerativePianoEngine {
|
|||
const midi = this.chooseMidi(source, register, this.lastBrushStreamMidi, true);
|
||||
const pan = this.getStylePan(styleIndex);
|
||||
const durationSeconds = clamp(
|
||||
this.generation.brushStream.durationBaseSeconds +
|
||||
intensity * this.generation.brushStream.durationIntensitySeconds -
|
||||
maniaAmount * this.generation.brushStream.durationManiaSeconds,
|
||||
this.generation.brushStream.durationMinSeconds,
|
||||
this.generation.brushStream.durationMaxSeconds
|
||||
generativePianoTuning.brushStream.durationBaseSeconds +
|
||||
intensity * generativePianoTuning.brushStream.durationIntensitySeconds -
|
||||
maniaAmount * generativePianoTuning.brushStream.durationManiaSeconds,
|
||||
generativePianoTuning.brushStream.durationMinSeconds,
|
||||
generativePianoTuning.brushStream.durationMaxSeconds
|
||||
);
|
||||
const delaySend = clamp(
|
||||
this.generation.brushStream.delaySendBase +
|
||||
intensity * this.generation.brushStream.delaySendIntensityWeight -
|
||||
maniaAmount * this.generation.brushStream.delaySendManiaWeight,
|
||||
this.generation.brushStream.delaySendMin,
|
||||
this.generation.brushStream.delaySendMax
|
||||
generativePianoTuning.brushStream.delaySendBase +
|
||||
intensity * generativePianoTuning.brushStream.delaySendIntensityWeight -
|
||||
maniaAmount * generativePianoTuning.brushStream.delaySendManiaWeight,
|
||||
generativePianoTuning.brushStream.delaySendMin,
|
||||
generativePianoTuning.brushStream.delaySendMax
|
||||
);
|
||||
|
||||
this.lastBrushStreamMidi = midi;
|
||||
|
|
@ -836,8 +829,8 @@ export class GenerativePianoEngine {
|
|||
this.playProfileNote(profile, {
|
||||
midi,
|
||||
velocity:
|
||||
(this.generation.brushStream.velocityBase +
|
||||
intensity * this.generation.brushStream.velocityIntensityWeight) *
|
||||
(generativePianoTuning.brushStream.velocityBase +
|
||||
intensity * generativePianoTuning.brushStream.velocityIntensityWeight) *
|
||||
styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime,
|
||||
durationSeconds,
|
||||
|
|
@ -848,46 +841,46 @@ export class GenerativePianoEngine {
|
|||
profile,
|
||||
midi,
|
||||
clamp01(
|
||||
this.generation.brushStream.lowpassBaseExpression +
|
||||
intensity * this.generation.brushStream.lowpassIntensityWeight +
|
||||
maniaAmount * this.generation.brushStream.lowpassManiaWeight
|
||||
generativePianoTuning.brushStream.lowpassBaseExpression +
|
||||
intensity * generativePianoTuning.brushStream.lowpassIntensityWeight +
|
||||
maniaAmount * generativePianoTuning.brushStream.lowpassManiaWeight
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
if (
|
||||
maniaAmount >= this.generation.brushStreamEcho.maniaThreshold &&
|
||||
(this.brushStreamNoteIndex % this.generation.brushStreamEcho.stepModulo ===
|
||||
this.generation.brushStreamEcho.stepRemainder ||
|
||||
intensity >= this.generation.brushStreamEcho.intensityThreshold)
|
||||
maniaAmount >= generativePianoTuning.brushStreamEcho.maniaThreshold &&
|
||||
(this.brushStreamNoteIndex % generativePianoTuning.brushStreamEcho.stepModulo ===
|
||||
generativePianoTuning.brushStreamEcho.stepRemainder ||
|
||||
intensity >= generativePianoTuning.brushStreamEcho.intensityThreshold)
|
||||
) {
|
||||
const echoMidi =
|
||||
midi + this.generation.brushStreamEcho.octaveSemitones <=
|
||||
this.generation.brushStreamEcho.maxMidi
|
||||
? midi + this.generation.brushStreamEcho.octaveSemitones
|
||||
: midi - this.generation.brushStreamEcho.octaveSemitones;
|
||||
midi + generativePianoTuning.brushStreamEcho.octaveSemitones <=
|
||||
generativePianoTuning.brushStreamEcho.maxMidi
|
||||
? midi + generativePianoTuning.brushStreamEcho.octaveSemitones
|
||||
: midi - generativePianoTuning.brushStreamEcho.octaveSemitones;
|
||||
this.playProfileNote(profile, {
|
||||
midi: echoMidi,
|
||||
velocity:
|
||||
(this.generation.brushStreamEcho.velocityBase +
|
||||
intensity * this.generation.brushStreamEcho.velocityIntensityWeight) *
|
||||
(generativePianoTuning.brushStreamEcho.velocityBase +
|
||||
intensity * generativePianoTuning.brushStreamEcho.velocityIntensityWeight) *
|
||||
styleVoices[styleIndex].velocityMultiplier,
|
||||
startTime: startTime + this.generation.brushMotifCanonDelaySeconds,
|
||||
startTime: startTime + generativePianoTuning.brushMotifCanonDelaySeconds,
|
||||
durationSeconds: Math.max(
|
||||
this.generation.brushStreamEcho.durationMinSeconds,
|
||||
durationSeconds * this.generation.brushStreamEcho.durationScale
|
||||
generativePianoTuning.brushStreamEcho.durationMinSeconds,
|
||||
durationSeconds * generativePianoTuning.brushStreamEcho.durationScale
|
||||
),
|
||||
pan: clamp(pan * this.generation.brushStreamEcho.panScale, -1, 1),
|
||||
pan: clamp(pan * generativePianoTuning.brushStreamEcho.panScale, -1, 1),
|
||||
role: 'brush',
|
||||
delaySend: Math.max(
|
||||
this.generation.brushStreamEcho.delaySendMin,
|
||||
delaySend * this.generation.brushStreamEcho.delaySendScale
|
||||
generativePianoTuning.brushStreamEcho.delaySendMin,
|
||||
delaySend * generativePianoTuning.brushStreamEcho.delaySendScale
|
||||
),
|
||||
lowpassHz: this.getLowpassHz(
|
||||
profile,
|
||||
echoMidi,
|
||||
this.generation.brushStreamEcho.lowpassBaseExpression +
|
||||
maniaAmount * this.generation.brushStreamEcho.lowpassManiaWeight
|
||||
generativePianoTuning.brushStreamEcho.lowpassBaseExpression +
|
||||
maniaAmount * generativePianoTuning.brushStreamEcho.lowpassManiaWeight
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
@ -905,8 +898,8 @@ export class GenerativePianoEngine {
|
|||
intensity:
|
||||
layer.energy *
|
||||
this.getBrushPhraseFade(layer, startTime) *
|
||||
(this.generation.brushPhrase.layerIntensityBase +
|
||||
layer.maniaAmount * this.generation.brushPhrase.layerIntensityManiaWeight),
|
||||
(generativePianoTuning.brushPhrase.layerIntensityBase +
|
||||
layer.maniaAmount * generativePianoTuning.brushPhrase.layerIntensityManiaWeight),
|
||||
}));
|
||||
const dominant = layerStates.reduce<{
|
||||
layer: BrushPhraseLayer;
|
||||
|
|
@ -924,10 +917,10 @@ export class GenerativePianoEngine {
|
|||
|
||||
return {
|
||||
intensity: clamp01(
|
||||
activity * this.generation.brushPhrase.frameActivityWeight +
|
||||
activity * generativePianoTuning.brushPhrase.frameActivityWeight +
|
||||
layeredIntensity +
|
||||
(dominant?.layer.maniaAmount ?? 0) *
|
||||
this.generation.brushPhrase.frameManiaWeight
|
||||
generativePianoTuning.brushPhrase.frameManiaWeight
|
||||
),
|
||||
layer: dominant?.layer ?? null,
|
||||
};
|
||||
|
|
@ -935,11 +928,11 @@ export class GenerativePianoEngine {
|
|||
|
||||
private getBrushStreamIntervalSteps(intensity: number): number {
|
||||
const intervalBeats =
|
||||
intensity >= this.generation.brushStream.intenseThreshold
|
||||
? this.generation.brushStreamIntenseIntervalBeats
|
||||
: intensity >= this.generation.brushStream.activeThreshold
|
||||
? this.generation.brushStreamActiveIntervalBeats
|
||||
: this.generation.brushStreamIdleIntervalBeats;
|
||||
intensity >= generativePianoTuning.brushStream.intenseThreshold
|
||||
? generativePianoTuning.brushStreamIntenseIntervalBeats
|
||||
: intensity >= generativePianoTuning.brushStream.activeThreshold
|
||||
? generativePianoTuning.brushStreamActiveIntervalBeats
|
||||
: generativePianoTuning.brushStreamIdleIntervalBeats;
|
||||
return Math.max(1, Math.round(intervalBeats * this.config.rhythm.stepsPerBeat));
|
||||
}
|
||||
|
||||
|
|
@ -950,11 +943,11 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
private getMotifOffset(strength: number): number {
|
||||
return strength >= this.generation.brushMotif.highThreshold
|
||||
? this.generation.brushMotif.highOffset
|
||||
: strength >= this.generation.brushMotif.mediumThreshold
|
||||
? this.generation.brushMotif.mediumOffset
|
||||
: this.generation.brushMotif.lowOffset;
|
||||
return strength >= generativePianoTuning.brushMotif.highThreshold
|
||||
? generativePianoTuning.brushMotif.highOffset
|
||||
: strength >= generativePianoTuning.brushMotif.mediumThreshold
|
||||
? generativePianoTuning.brushMotif.mediumOffset
|
||||
: generativePianoTuning.brushMotif.lowOffset;
|
||||
}
|
||||
|
||||
private getBrushMotifDegrees({
|
||||
|
|
@ -986,17 +979,17 @@ export class GenerativePianoEngine {
|
|||
maniaAmount: number
|
||||
): GardenAudioRegister {
|
||||
const shift = Math.round(
|
||||
maniaAmount * this.generation.registerBias.maniaShiftSemitones
|
||||
maniaAmount * generativePianoTuning.registerBias.maniaShiftSemitones
|
||||
);
|
||||
const midiMin = clamp(
|
||||
register.midiMin + shift,
|
||||
this.generation.registerBias.midiMin,
|
||||
this.generation.registerBias.midiMaxForMin
|
||||
generativePianoTuning.registerBias.midiMin,
|
||||
generativePianoTuning.registerBias.midiMaxForMin
|
||||
);
|
||||
const midiMax = clamp(
|
||||
register.midiMax + shift,
|
||||
midiMin + this.generation.registerBias.minimumSpan,
|
||||
this.generation.registerBias.midiMax
|
||||
midiMin + generativePianoTuning.registerBias.minimumSpan,
|
||||
generativePianoTuning.registerBias.midiMax
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
@ -1039,8 +1032,8 @@ export class GenerativePianoEngine {
|
|||
|
||||
pitchSource.offsets.forEach((offset, preference) => {
|
||||
for (
|
||||
let octave = this.generation.candidateOctaveSearch.min;
|
||||
octave <= this.generation.candidateOctaveSearch.max;
|
||||
let octave = generativePianoTuning.candidateOctaveSearch.min;
|
||||
octave <= generativePianoTuning.candidateOctaveSearch.max;
|
||||
octave += 1
|
||||
) {
|
||||
const midi = pitchSource.baseMidi + offset + octave * PITCH_SEMITONES_PER_OCTAVE;
|
||||
|
|
@ -1067,38 +1060,38 @@ export class GenerativePianoEngine {
|
|||
return (
|
||||
Math.abs(candidate.midi - previousMidi) +
|
||||
Math.abs(candidate.midi - register.preferredMidi) *
|
||||
this.generation.noteScoreRegisterWeight +
|
||||
candidate.preference * this.generation.noteScorePreferenceWeight +
|
||||
candidate.chordToneDistance * this.generation.noteScoreChordToneWeight +
|
||||
generativePianoTuning.noteScoreRegisterWeight +
|
||||
candidate.preference * generativePianoTuning.noteScorePreferenceWeight +
|
||||
candidate.chordToneDistance * generativePianoTuning.noteScoreChordToneWeight +
|
||||
(avoidRepeat && candidate.midi === previousMidi
|
||||
? this.generation.noteScoreRepeatPenalty
|
||||
? generativePianoTuning.noteScoreRepeatPenalty
|
||||
: 0)
|
||||
);
|
||||
}
|
||||
|
||||
private shouldPlaySupport(expression: number, barIndex: number): boolean {
|
||||
if (expression >= this.generation.supportNote.expressionThreshold) {
|
||||
if (expression >= generativePianoTuning.supportNote.expressionThreshold) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
barIndex % this.generation.supportBarSpacing === this.generation.supportBarOffset
|
||||
barIndex % generativePianoTuning.supportBarSpacing === generativePianoTuning.supportBarOffset
|
||||
);
|
||||
}
|
||||
|
||||
private shouldPlayTexture(expression: number, barIndex: number): boolean {
|
||||
const spacing =
|
||||
expression < this.generation.textureNote.idleExpressionThreshold
|
||||
? this.generation.idleTextureBarSpacing
|
||||
: expression < this.generation.textureNote.mediumExpressionThreshold
|
||||
? this.generation.mediumTextureBarSpacing
|
||||
: this.generation.textureNote.intenseSpacing;
|
||||
expression < generativePianoTuning.textureNote.idleExpressionThreshold
|
||||
? generativePianoTuning.idleTextureBarSpacing
|
||||
: expression < generativePianoTuning.textureNote.mediumExpressionThreshold
|
||||
? generativePianoTuning.mediumTextureBarSpacing
|
||||
: generativePianoTuning.textureNote.intenseSpacing;
|
||||
|
||||
return (
|
||||
barIndex % spacing ===
|
||||
(spacing === this.generation.textureNote.intenseSpacing
|
||||
(spacing === generativePianoTuning.textureNote.intenseSpacing
|
||||
? 0
|
||||
: this.generation.textureNote.idlePhase)
|
||||
: generativePianoTuning.textureNote.idlePhase)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1106,14 +1099,14 @@ export class GenerativePianoEngine {
|
|||
chordIntervals: ReadonlyArray<number>,
|
||||
styleIndex: GardenAudioStyleIndex
|
||||
): Array<number> {
|
||||
return this.generation.supportNote.offsetsByStyle[styleIndex].map((offset) =>
|
||||
return generativePianoTuning.supportNote.offsetsByStyle[styleIndex].map((offset) =>
|
||||
getConfiguredChordOffset(chordIntervals, offset)
|
||||
);
|
||||
}
|
||||
|
||||
private getChord(profile: GardenAudioVibeProfile, barIndex: number): GardenAudioChord {
|
||||
const progressionIndex =
|
||||
Math.floor(barIndex / this.generation.chordBars) % profile.progression.length;
|
||||
Math.floor(barIndex / generativePianoTuning.chordBars) % profile.progression.length;
|
||||
return profile.progression[progressionIndex];
|
||||
}
|
||||
|
||||
|
|
@ -1122,17 +1115,17 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
private getStyleIndex(startTime: number): GardenAudioStyleIndex {
|
||||
const styleCount = this.generation.stylePools.length;
|
||||
const rotationBars = Math.max(1, Math.round(this.generation.styleRotationBars));
|
||||
const styleCount = generativePianoTuning.stylePools.length;
|
||||
const rotationBars = Math.max(1, Math.round(generativePianoTuning.styleRotationBars));
|
||||
return (Math.floor(this.getGlobalBarIndex(startTime) / rotationBars) %
|
||||
styleCount) as GardenAudioStyleIndex;
|
||||
}
|
||||
|
||||
private getStylePan(styleIndex: GardenAudioStyleIndex): number {
|
||||
const pool = this.generation.stylePools[styleIndex];
|
||||
const pool = generativePianoTuning.stylePools[styleIndex];
|
||||
const styleVoice = styleVoices[styleIndex];
|
||||
return clamp(
|
||||
pool.pan + styleVoice.panOffset * this.generation.stylePanOffsetScale,
|
||||
pool.pan + styleVoice.panOffset * generativePianoTuning.stylePanOffsetScale,
|
||||
-1,
|
||||
1
|
||||
);
|
||||
|
|
@ -1145,13 +1138,13 @@ export class GenerativePianoEngine {
|
|||
): number {
|
||||
const midiLift =
|
||||
clamp01(
|
||||
(midi - this.generation.lowpass.midiBase) / this.generation.lowpass.midiRange
|
||||
) * this.generation.lowpass.midiLiftHz;
|
||||
(midi - generativePianoTuning.lowpass.midiBase) / generativePianoTuning.lowpass.midiRange
|
||||
) * generativePianoTuning.lowpass.midiLiftHz;
|
||||
return clamp(
|
||||
this.config.piano.lowpassHz *
|
||||
profile.brightness *
|
||||
(this.generation.lowpass.expressionBase +
|
||||
expression * this.generation.lowpass.expressionWeight) +
|
||||
(generativePianoTuning.lowpass.expressionBase +
|
||||
expression * generativePianoTuning.lowpass.expressionWeight) +
|
||||
midiLift,
|
||||
this.config.piano.lowpassMinHz,
|
||||
this.config.piano.lowpassMaxHz
|
||||
|
|
@ -1174,14 +1167,14 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
private getExpression(activity: number): number {
|
||||
const liftedActivity = Math.max(
|
||||
activity,
|
||||
this.activeProfile?.idleIntensity ?? this.config.rhythm.idleIntensity
|
||||
);
|
||||
return clamp01(
|
||||
(liftedActivity - this.config.rhythm.sparseActivity) /
|
||||
const activityExpression = clamp01(
|
||||
(activity - this.config.rhythm.sparseActivity) /
|
||||
(1 - this.config.rhythm.sparseActivity)
|
||||
);
|
||||
const idleExpression = clamp01(
|
||||
this.activeProfile?.idleIntensity ?? this.config.rhythm.idleIntensity
|
||||
);
|
||||
return Math.max(activityExpression, idleExpression);
|
||||
}
|
||||
|
||||
private getBeatDurationSeconds(): number {
|
||||
|
|
@ -1263,7 +1256,7 @@ export class GenerativePianoEngine {
|
|||
private reserveBrushStreamNote(stepIndex: number): boolean {
|
||||
const barIndex = this.getBarIndexForStep(stepIndex);
|
||||
const noteCount = this.brushStreamNoteCountsByBar.get(barIndex) ?? 0;
|
||||
if (noteCount >= this.generation.maxBrushStreamNotesPerBar) {
|
||||
if (noteCount >= generativePianoTuning.maxBrushStreamNotesPerBar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,10 +88,7 @@ export class PianoSampler {
|
|||
const sample = this.findNearestSample(midi);
|
||||
|
||||
if (sample) {
|
||||
const noteGainValue = Math.max(
|
||||
pianoSamplerTuning.minGain,
|
||||
this.config.piano.gain * noteVelocity
|
||||
);
|
||||
const noteGainValue = this.computeNoteGain(noteVelocity);
|
||||
const sustainSeconds =
|
||||
profileSustainSeconds *
|
||||
(this.config.piano.sustainBase +
|
||||
|
|
@ -143,9 +140,9 @@ export class PianoSampler {
|
|||
return;
|
||||
}
|
||||
|
||||
const noteGainValue = Math.max(
|
||||
pianoSamplerTuning.minGain,
|
||||
this.config.piano.gain * noteVelocity * pianoSamplerTuning.synthGainScale
|
||||
const noteGainValue = this.computeNoteGain(
|
||||
noteVelocity,
|
||||
pianoSamplerTuning.synthGainScale
|
||||
);
|
||||
const releaseAt =
|
||||
scheduledStart +
|
||||
|
|
@ -277,6 +274,13 @@ export class PianoSampler {
|
|||
);
|
||||
}
|
||||
|
||||
private computeNoteGain(velocity: number, scale = 1): number {
|
||||
return Math.max(
|
||||
pianoSamplerTuning.minGain,
|
||||
this.config.piano.gain * velocity * scale
|
||||
);
|
||||
}
|
||||
|
||||
private findNearestSample(midi: number): LoadedPianoSample | null {
|
||||
if (this.samples.length === 0) {
|
||||
return null;
|
||||
|
|
|
|||
Loading…
Reference in a new issue