This commit is contained in:
parent
6bc125be1c
commit
ed5a4379db
76 changed files with 1418 additions and 988 deletions
|
|
@ -22,12 +22,22 @@ export interface GardenAudioVibeProfile extends GardenAudioVibeSettings {
|
|||
progression: Array<GardenAudioChord>;
|
||||
}
|
||||
|
||||
export const defaultGardenAudioVibeSettings: GardenAudioVibeSettings = {
|
||||
idleIntensity: 0.08,
|
||||
bpm: 74,
|
||||
rampUpIntensity: 0.85,
|
||||
rampUpTime: 0.08,
|
||||
noteLength: 0.42,
|
||||
notePitchOffset: 0,
|
||||
brightness: 1,
|
||||
};
|
||||
|
||||
export const createGardenAudioConfig = () => ({
|
||||
masterVolume: DEFAULT_AUDIO_VOLUME,
|
||||
fadeInSeconds: 0.45,
|
||||
updateRampSeconds: 0.08,
|
||||
delay: {
|
||||
timeSeconds: 0.46,
|
||||
timeSeconds: 0.405,
|
||||
feedback: 0.12,
|
||||
wetGain: 0.044,
|
||||
erasingActivity: 0.12,
|
||||
|
|
@ -43,9 +53,9 @@ export const createGardenAudioConfig = () => ({
|
|||
maxVoices: 24,
|
||||
gain: 0.48,
|
||||
sustainSeconds: 0.42,
|
||||
sustainLevel: 0.32,
|
||||
releaseSeconds: 0.24,
|
||||
lowpassHz: 7600,
|
||||
sustainLevel: 0.26,
|
||||
releaseSeconds: 0.34,
|
||||
lowpassHz: 7000,
|
||||
gainAttackSeconds: 0.006,
|
||||
lowpassMaxHz: 12000,
|
||||
lowpassMinHz: 1400,
|
||||
|
|
@ -53,8 +63,8 @@ export const createGardenAudioConfig = () => ({
|
|||
sustainVelocityRange: 0.55,
|
||||
},
|
||||
rhythm: {
|
||||
idleIntensity: 0.08,
|
||||
bpm: 74,
|
||||
idleIntensity: defaultGardenAudioVibeSettings.idleIntensity,
|
||||
bpm: defaultGardenAudioVibeSettings.bpm,
|
||||
stepsPerBeat: 4,
|
||||
stepsPerBar: 16,
|
||||
sparseActivity: 0.055,
|
||||
|
|
@ -69,9 +79,7 @@ export const createGardenAudioConfig = () => ({
|
|||
pianoActivity: 0,
|
||||
},
|
||||
energy: {
|
||||
attackSeconds: 0.08,
|
||||
decaySeconds: 0.9,
|
||||
immediateActivityScale: 0.85,
|
||||
releaseSeconds: 1.15,
|
||||
strokeDecaySeconds: 0.32,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ export const getStrokeMetrics = (stroke: GardenAudioStroke): GardenAudioStrokeMe
|
|||
const dy = stroke.to[1] - stroke.from[1];
|
||||
const distancePixels = Math.hypot(dx, dy);
|
||||
const elapsedSeconds = Math.max(minElapsedSeconds, stroke.elapsedSeconds ?? 0);
|
||||
const normalizationPixels = Math.max(1, Math.min(stroke.canvasSize[0], stroke.canvasSize[1]));
|
||||
const normalizationPixels = Math.max(
|
||||
1,
|
||||
Math.min(stroke.canvasSize[0], stroke.canvasSize[1])
|
||||
);
|
||||
const normalizedDistance = distancePixels / normalizationPixels;
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -260,11 +260,10 @@ export class GardenAudio {
|
|||
}
|
||||
|
||||
public stroke(stroke: GardenAudioStroke): void {
|
||||
if (this.lifecycle === 'destroyed' || this.isMuted) {
|
||||
if (this.lifecycle !== 'started' || this.isMuted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.start(stroke.vibe);
|
||||
const context = this.graph.context;
|
||||
if (!context) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -207,8 +207,8 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
},
|
||||
{
|
||||
midiMin: 62,
|
||||
midiMax: 81,
|
||||
preferredMidi: 72,
|
||||
midiMax: 78,
|
||||
preferredMidi: 70,
|
||||
pan: 0.18,
|
||||
scaleDegrees: [2, 3, 4, 6],
|
||||
},
|
||||
|
|
@ -254,10 +254,10 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
expressionMultiplier: 0.9,
|
||||
},
|
||||
padChord: {
|
||||
velocities: [0.052, 0.041, 0.033],
|
||||
expressionVelocityWeight: 0.02,
|
||||
velocities: [0.046, 0.036, 0.029],
|
||||
expressionVelocityWeight: 0.018,
|
||||
delaySend: 0.008,
|
||||
lowpassExpressionWeight: 0.28,
|
||||
lowpassExpressionWeight: 0.24,
|
||||
},
|
||||
supportNote: {
|
||||
velocityBase: 0.105,
|
||||
|
|
@ -316,7 +316,7 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
brushStream: {
|
||||
inferredManiaThreshold: 0.82,
|
||||
inferredManiaRange: 0.18,
|
||||
registerManiaShift: 0.45,
|
||||
registerManiaShift: 0.3,
|
||||
chordToneEverySteps: 4,
|
||||
durationBaseSeconds: 0.48,
|
||||
durationIntensitySeconds: 0.08,
|
||||
|
|
@ -329,22 +329,22 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
delaySendMin: 0.006,
|
||||
delaySendMax: 0.032,
|
||||
velocityBase: 0.1,
|
||||
velocityIntensityWeight: 0.13,
|
||||
velocityIntensityWeight: 0.1,
|
||||
lowpassBaseExpression: 0.39,
|
||||
lowpassIntensityWeight: 0.48,
|
||||
lowpassManiaWeight: 0.18,
|
||||
intenseThreshold: 0.62,
|
||||
intenseThreshold: 0.68,
|
||||
activeThreshold: 0.34,
|
||||
},
|
||||
brushStreamEcho: {
|
||||
maniaThreshold: 0.86,
|
||||
stepModulo: 2,
|
||||
maniaThreshold: 0.92,
|
||||
stepModulo: 3,
|
||||
stepRemainder: 1,
|
||||
intensityThreshold: 0.95,
|
||||
octaveSemitones: 12,
|
||||
maxMidi: 88,
|
||||
velocityBase: 0.045,
|
||||
velocityIntensityWeight: 0.05,
|
||||
maxMidi: 84,
|
||||
velocityBase: 0.035,
|
||||
velocityIntensityWeight: 0.04,
|
||||
durationMinSeconds: 0.11,
|
||||
durationScale: 0.68,
|
||||
panScale: -0.75,
|
||||
|
|
@ -361,7 +361,7 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
lowOffset: -1,
|
||||
},
|
||||
registerBias: {
|
||||
maniaShiftSemitones: 4,
|
||||
maniaShiftSemitones: 2,
|
||||
midiMin: 36,
|
||||
midiMaxForMin: 86,
|
||||
minimumSpan: 4,
|
||||
|
|
@ -375,7 +375,7 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
lowpass: {
|
||||
midiBase: 48,
|
||||
midiRange: 33,
|
||||
midiLiftHz: 720,
|
||||
midiLiftHz: 500,
|
||||
expressionBase: 0.58,
|
||||
expressionWeight: 0.32,
|
||||
},
|
||||
|
|
@ -396,16 +396,16 @@ export const generativePianoTuning: GenerativePianoTuning = {
|
|||
strokeAccentMinSteps: 12,
|
||||
strokeAccentThreshold: 0.58,
|
||||
maxBrushPhraseLayers: 3,
|
||||
maxBrushStreamNotesPerBar: 9,
|
||||
maxBrushStreamNotesPerBar: 7,
|
||||
brushLayerBaseSeconds: 5.5,
|
||||
brushLayerEnergySeconds: 2.5,
|
||||
brushLayerMinIntensity: 0.12,
|
||||
brushStreamIdleIntervalBeats: 2,
|
||||
brushStreamActiveIntervalBeats: 1,
|
||||
brushStreamIntenseIntervalBeats: 0.5,
|
||||
brushStreamIntenseIntervalBeats: 0.75,
|
||||
brushMotifMaxSteps: 8,
|
||||
brushMotifCanonDelaySeconds: 0.055,
|
||||
padDurationBarScale: 0.46,
|
||||
padDurationBarScale: 0.82,
|
||||
};
|
||||
|
||||
export const styleVoices: [
|
||||
|
|
|
|||
|
|
@ -196,7 +196,8 @@ export class GenerativePianoEngine {
|
|||
|
||||
if (
|
||||
this.isWaitingForGestureAccent &&
|
||||
now - this.lastGestureAccentAt >= generativePianoTuning.gestureAccentMinIntervalSeconds
|
||||
now - this.lastGestureAccentAt >=
|
||||
generativePianoTuning.gestureAccentMinIntervalSeconds
|
||||
) {
|
||||
this.recordTouchDown({
|
||||
vibe,
|
||||
|
|
@ -554,7 +555,9 @@ export class GenerativePianoEngine {
|
|||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const degrees = this.rotate(
|
||||
pool.scaleDegrees,
|
||||
Math.round(strength * generativePianoTuning.gestureAccent.rotationStrengthMultiplier)
|
||||
Math.round(
|
||||
strength * generativePianoTuning.gestureAccent.rotationStrengthMultiplier
|
||||
)
|
||||
);
|
||||
|
||||
const midi = this.chooseMidi(
|
||||
|
|
@ -705,7 +708,9 @@ export class GenerativePianoEngine {
|
|||
);
|
||||
layer.motifOffsets.push(this.getMotifOffset(strength));
|
||||
if (layer.motifOffsets.length > generativePianoTuning.brushMotifMaxSteps) {
|
||||
layer.motifOffsets = layer.motifOffsets.slice(-generativePianoTuning.brushMotifMaxSteps);
|
||||
layer.motifOffsets = layer.motifOffsets.slice(
|
||||
-generativePianoTuning.brushMotifMaxSteps
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -789,7 +794,9 @@ export class GenerativePianoEngine {
|
|||
const chordIntervals = getChordIntervals(chord, false);
|
||||
const rootMidi = profile.rootMidi + chord.rootOffset;
|
||||
const useChordTone =
|
||||
this.brushStreamNoteIndex % generativePianoTuning.brushStream.chordToneEverySteps === 0;
|
||||
this.brushStreamNoteIndex %
|
||||
generativePianoTuning.brushStream.chordToneEverySteps ===
|
||||
0;
|
||||
const source = useChordTone
|
||||
? {
|
||||
baseMidi: rootMidi,
|
||||
|
|
@ -897,7 +904,8 @@ export class GenerativePianoEngine {
|
|||
layer.energy *
|
||||
this.getBrushPhraseFade(layer, startTime) *
|
||||
(generativePianoTuning.brushPhrase.layerIntensityBase +
|
||||
layer.maniaAmount * generativePianoTuning.brushPhrase.layerIntensityManiaWeight),
|
||||
layer.maniaAmount *
|
||||
generativePianoTuning.brushPhrase.layerIntensityManiaWeight),
|
||||
}));
|
||||
const dominant = layerStates.reduce<{
|
||||
layer: BrushPhraseLayer;
|
||||
|
|
@ -1073,7 +1081,8 @@ export class GenerativePianoEngine {
|
|||
}
|
||||
|
||||
return (
|
||||
barIndex % generativePianoTuning.supportBarSpacing === generativePianoTuning.supportBarOffset
|
||||
barIndex % generativePianoTuning.supportBarSpacing ===
|
||||
generativePianoTuning.supportBarOffset
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1136,7 +1145,8 @@ export class GenerativePianoEngine {
|
|||
): number {
|
||||
const midiLift =
|
||||
clamp01(
|
||||
(midi - generativePianoTuning.lowpass.midiBase) / generativePianoTuning.lowpass.midiRange
|
||||
(midi - generativePianoTuning.lowpass.midiBase) /
|
||||
generativePianoTuning.lowpass.midiRange
|
||||
) * generativePianoTuning.lowpass.midiLiftHz;
|
||||
return clamp(
|
||||
this.config.piano.lowpassHz *
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import { getLoadedPianoSamples, loadPianoSamples } from './piano-samples';
|
|||
|
||||
export const PIANO_SCHEDULE_AHEAD_SECONDS = 0.002;
|
||||
|
||||
type PianoLoadState = 'idle' | 'loading' | 'loaded';
|
||||
|
||||
interface ActivePianoVoice {
|
||||
gain: GainNode;
|
||||
source: AudioScheduledSourceNode;
|
||||
|
|
@ -27,7 +25,6 @@ const pianoSamplerTuning = {
|
|||
};
|
||||
|
||||
export class PianoSampler {
|
||||
private loadState: PianoLoadState = 'idle';
|
||||
private samples: Array<LoadedPianoSample> = [];
|
||||
private activeVoices: Array<ActivePianoVoice> = [];
|
||||
|
||||
|
|
@ -37,27 +34,19 @@ export class PianoSampler {
|
|||
) {}
|
||||
|
||||
public load(context: BaseAudioContext): Promise<void> {
|
||||
if (this.loadState === 'loaded') {
|
||||
if (this.samples.length > 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const loadedSamples = getLoadedPianoSamples();
|
||||
if (loadedSamples) {
|
||||
this.setSamples(loadedSamples);
|
||||
this.loadState = 'loaded';
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.loadState = 'loading';
|
||||
return loadPianoSamples(context)
|
||||
.then((samples) => {
|
||||
this.setSamples(samples);
|
||||
this.loadState = 'loaded';
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loadState = 'idle';
|
||||
throw error;
|
||||
});
|
||||
return loadPianoSamples(context).then((samples) => {
|
||||
this.setSamples(samples);
|
||||
});
|
||||
}
|
||||
|
||||
public play({
|
||||
|
|
@ -154,7 +143,6 @@ export class PianoSampler {
|
|||
}
|
||||
|
||||
public reset(): void {
|
||||
this.loadState = 'idle';
|
||||
this.samples = [];
|
||||
this.activeVoices = [];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue