liminal-sessions / src /utils /configLoader.ts
Severian's picture
Upload 32 files
6a03d7f verified
// Configuration loader and manager for the visualizer
import type { ParticleSystemParams } from '../types/visualization'
export interface VisualizerConfig {
visualization: {
general: {
maxSpheres: number
defaultActiveSpheres: number
performanceMode: boolean
debugLogging: boolean
debugLogFrequency: number
}
particles: {
defaultParticleCount: number
minParticleCount: number
maxParticleCount: number
particleSize: number
particleLifetime: number
particleSizeVariation: number
particleOpacity: number
}
spheres: {
baseRadius: number
radiusIncrement: number
innerSphereRadius: number
maxSphereRadius: number
containmentPullback: number
overflowDamping: number
}
physics: {
turbulenceStrength: number
noiseScale: number
noiseScaleVariation: number
noiseSpeed: number
defaultDamping: number
velocityDecay: number
beatForceMultiplier: number
maxVelocity: number
stabilityThreshold: number
emergencyDamping: number
}
rotation: {
rotationSpeedMin: number
rotationSpeedMax: number
rotationSpeedIncrement: number
rotationSmoothness: number
autoRotateSpeed: number
}
}
audio: {
frequencyRanges: Array<{
name: string
minFrequency: number
maxFrequency: number
beatThreshold: number
volumeChangeThreshold: number
}>
beatDetection: {
energyHistoryLength: number
minTimeBetweenPeaks: number
beatStrength: number
waveDecayRate: number
peakThresholdMultiplier: number
}
volumeSmoothing: {
enabled: boolean
smoothingFactor: number
changeThresholdDefault: number
}
}
entronaut: {
enabled: boolean
sefaAnalysis: {
fieldSize: number
fieldWidth: number
fieldHeight: number
updateFrequency: number
temporalSmoothing: number
intensitySmoothing: number
}
weights: {
alpha_amplitude: number
alpha_phase: number
alpha_entropy: number
alpha_curvature: number
alpha_spectral: number
}
adaptiveCoupling: {
enabled: boolean
updateFrequency: number
dampingBase: number
dampingVariation: number
diffusionBase: number
diffusionVariation: number
couplingBase: number
couplingVariation: number
flockingRadius: number
flockingInfluence: number
metabolicPulseStrength: number
neighborCheckStep: number
neighborRange: number
}
biomimeticBehavior: {
collectiveMemory: number
emergenceThreshold: number
coherenceWeight: number
complexityWeight: number
networkInfluence: number
syncPulseStrength: number
}
}
tendrils: {
enabled: boolean
maxTendrils: number
defaultDensity: number
maxTendrilLength: number
tendrilSegments: number
updateInterval: number
sampleParticlesPerSphere: number
particleSampleStep: number
curvature: {
midPointMultiplier: number
undulationStrength: number
timeFactorBase: number
timeFactorCurve: number
spatialFrequency: number
}
visual: {
baseOpacity: number
glowIntensity: number
colorIntensityBase: number
colorIntensityVariation: number
blendingMode: string
}
}
cymatics: {
enabled: boolean
intensity: number
patterns: {
radialWaves: {
frequencyFactor: number
petalCount: number
amplitudeZ: number
}
sphericalHarmonics: {
lMultiplier: number
mMultiplier: number
timeMultiplier: number
}
geometricLattice: {
frequencyFactor: number
}
flowerOfLife: {
frequencyFactor: number
basePetals: number
petalVariation: number
amplitudeZ: number
}
}
breathing: {
enabled: boolean
speed: number
intensityBase: number
intensityVariation: number
}
}
colors: {
sphereColorSchemes: Array<{
name: string
start: string
end: string
}>
tendrilColors: {
baseColor: [number, number, number]
frequencyColorMap: {
lowFreq: [number, number, number]
midFreq: [number, number, number]
highFreq: [number, number, number]
}
}
}
camera: {
defaultPosition: [number, number, number]
fov: number
near: number
far: number
controls: {
enableDamping: boolean
dampingFactor: number
maxDistance: number
minDistance: number
maxPolarAngle: number
autoRotateSpeed: number
}
presets: {
[key: string]: [number, number, number]
}
}
fog: {
enabled: boolean
color: string
near: number
far: number
density: number
}
rendering: {
antialias: boolean
background: string
particleBlending: string
targetFPS: number
adaptiveQuality: {
enabled: boolean
minFPS: number
qualityReductionStep: number
qualityRecoveryDelay: number
}
}
ui: {
controls: {
defaultCollapsed: boolean
position: string
maxWidth: string
opacity: number
}
about: {
defaultCollapsed: boolean
position: string
width: string
}
animation: {
transitionDuration: string
hoverScale: number
backdropBlur: string
}
}
performance: {
adaptiveSettings: {
enabled: boolean
thresholds: {
particleReduction: number
tendrilReduction: number
sefaDisable: number
couplingDisable: number
}
}
optimizations: {
particleUpdateStep: number
tendrilUpdateStep: number
sefaUpdateStep: number
couplingUpdateStep: number
neighborCheckReduction: number
}
}
presets: {
[key: string]: Partial<VisualizerConfig>
}
}
class ConfigLoader {
private config: VisualizerConfig | null = null
private loadPromise: Promise<VisualizerConfig> | null = null
async loadConfig(): Promise<VisualizerConfig> {
if (this.config) {
return this.config
}
if (this.loadPromise) {
return this.loadPromise
}
this.loadPromise = this.fetchConfig()
this.config = await this.loadPromise
return this.config
}
private async fetchConfig(): Promise<VisualizerConfig> {
try {
const response = await fetch('/config.json')
if (!response.ok) {
throw new Error(`Failed to load config: ${response.statusText}`)
}
const config = await response.json()
console.log('✅ Configuration loaded successfully')
return config
} catch (error) {
console.error('❌ Failed to load configuration, using defaults:', error)
return this.getDefaultConfig()
}
}
private getDefaultConfig(): VisualizerConfig {
// Fallback default configuration
return {
visualization: {
general: {
maxSpheres: 5,
defaultActiveSpheres: 5,
performanceMode: false,
debugLogging: true,
debugLogFrequency: 0.005
},
particles: {
defaultParticleCount: 15000,
minParticleCount: 1000,
maxParticleCount: 30000,
particleSize: 1.0,
particleLifetime: 20.0,
particleSizeVariation: 0.2,
particleOpacity: 0.8
},
spheres: {
baseRadius: 1.0,
radiusIncrement: 0.08,
innerSphereRadius: 0.7,
maxSphereRadius: 2.0,
containmentPullback: 0.1,
overflowDamping: 0.9
},
physics: {
turbulenceStrength: 0.008,
noiseScale: 3.0,
noiseScaleVariation: 0.4,
noiseSpeed: 0.5,
defaultDamping: 0.95,
velocityDecay: 0.95,
beatForceMultiplier: 0.001,
maxVelocity: 0.1,
stabilityThreshold: 10.0,
emergencyDamping: 0.1
},
rotation: {
rotationSpeedMin: 0.001,
rotationSpeedMax: 0.06,
rotationSpeedIncrement: 0.008,
rotationSmoothness: 0.1,
autoRotateSpeed: 0.5
}
},
audio: {
frequencyRanges: [
{ name: "sub-bass", minFrequency: 20, maxFrequency: 80, beatThreshold: 0.6, volumeChangeThreshold: 0.05 },
{ name: "bass", minFrequency: 120, maxFrequency: 250, beatThreshold: 0.65, volumeChangeThreshold: 0.08 },
{ name: "mid", minFrequency: 250, maxFrequency: 800, beatThreshold: 0.7, volumeChangeThreshold: 0.1 },
{ name: "high-mid", minFrequency: 1000, maxFrequency: 4000, beatThreshold: 0.75, volumeChangeThreshold: 0.12 },
{ name: "high", minFrequency: 5000, maxFrequency: 10000, beatThreshold: 0.8, volumeChangeThreshold: 0.15 }
],
beatDetection: {
energyHistoryLength: 30,
minTimeBetweenPeaks: 200,
beatStrength: 0.02,
waveDecayRate: 0.98,
peakThresholdMultiplier: 1.3
},
volumeSmoothing: {
enabled: true,
smoothingFactor: 0.1,
changeThresholdDefault: 0.1
}
},
entronaut: {
enabled: true,
sefaAnalysis: {
fieldSize: 1024,
fieldWidth: 32,
fieldHeight: 32,
updateFrequency: 3,
temporalSmoothing: 0.9,
intensitySmoothing: 0.1
},
weights: {
alpha_amplitude: 1.2,
alpha_phase: 0.8,
alpha_entropy: 1.5,
alpha_curvature: 1.0,
alpha_spectral: 1.0
},
adaptiveCoupling: {
enabled: true,
updateFrequency: 4,
dampingBase: 0.9,
dampingVariation: 0.3,
diffusionBase: 0.01,
diffusionVariation: 0.6,
couplingBase: 0.02,
couplingVariation: 0.04,
flockingRadius: 0.25,
flockingInfluence: 0.2,
metabolicPulseStrength: 0.5,
neighborCheckStep: 5,
neighborRange: 20
},
biomimeticBehavior: {
collectiveMemory: 0.9,
emergenceThreshold: 0.5,
coherenceWeight: 1.0,
complexityWeight: 1.0,
networkInfluence: 0.1,
syncPulseStrength: 0.2
}
},
tendrils: {
enabled: true,
maxTendrils: 2000,
defaultDensity: 0.3,
maxTendrilLength: 0.8,
tendrilSegments: 8,
updateInterval: 100,
sampleParticlesPerSphere: 10,
particleSampleStep: 1000,
curvature: {
midPointMultiplier: 0.2,
undulationStrength: 0.05,
timeFactorBase: 0.001,
timeFactorCurve: 0.0015,
spatialFrequency: 10
},
visual: {
baseOpacity: 0.3,
glowIntensity: 1.0,
colorIntensityBase: 0.3,
colorIntensityVariation: 0.7,
blendingMode: "additive"
}
},
cymatics: {
enabled: true,
intensity: 0.002,
patterns: {
radialWaves: { frequencyFactor: 0.1, petalCount: 6, amplitudeZ: 0.5 },
sphericalHarmonics: { lMultiplier: 0.5, mMultiplier: 0.3, timeMultiplier: 2 },
geometricLattice: { frequencyFactor: 0.2 },
flowerOfLife: { frequencyFactor: 0.15, basePetals: 6, petalVariation: 0.1, amplitudeZ: 0.3 }
},
breathing: { enabled: true, speed: 0.5, intensityBase: 0.3, intensityVariation: 0.7 }
},
colors: {
sphereColorSchemes: [
{ name: "sub-bass", start: "#ff3366", end: "#3366ff" },
{ name: "bass", start: "#ff6b35", end: "#c44569" },
{ name: "mid", start: "#f7931e", end: "#f8b500" },
{ name: "high-mid", start: "#ffe66d", end: "#feca57" },
{ name: "high", start: "#4ecdc4", end: "#45b7d1" }
],
tendrilColors: {
baseColor: [0.3, 0.3, 0.3],
frequencyColorMap: {
lowFreq: [0.8, 0.2, 0.2],
midFreq: [0.2, 0.8, 0.2],
highFreq: [0.2, 0.2, 0.8]
}
}
},
camera: {
defaultPosition: [0, 0, 2.5],
fov: 75,
near: 0.1,
far: 1000,
controls: {
enableDamping: true,
dampingFactor: 0.05,
maxDistance: 50,
minDistance: 0.1,
maxPolarAngle: 3.14159,
autoRotateSpeed: 0.5
},
presets: {
default: [0, 0, 2.5],
inside: [0, 0, 0.5],
far: [0, 0, 8],
top: [0, 5, 0],
side: [5, 0, 0]
}
},
fog: {
enabled: true,
color: "#000000",
near: 2.7,
far: 3.7,
density: 0.1
},
rendering: {
antialias: true,
background: "#000000",
particleBlending: "additive",
targetFPS: 60,
adaptiveQuality: {
enabled: true,
minFPS: 30,
qualityReductionStep: 0.1,
qualityRecoveryDelay: 2000
}
},
ui: {
controls: {
defaultCollapsed: true,
position: "bottom-right",
maxWidth: "250px",
opacity: 0.8
},
about: {
defaultCollapsed: true,
position: "bottom-center",
width: "350px"
},
animation: {
transitionDuration: "0.3s",
hoverScale: 1.1,
backdropBlur: "10px"
}
},
performance: {
adaptiveSettings: {
enabled: true,
thresholds: {
particleReduction: 25,
tendrilReduction: 20,
sefaDisable: 15,
couplingDisable: 10
}
},
optimizations: {
particleUpdateStep: 1,
tendrilUpdateStep: 4,
sefaUpdateStep: 10,
couplingUpdateStep: 4,
neighborCheckReduction: 5
}
},
presets: {}
}
}
applyPreset(presetName: string): void {
if (!this.config) {
console.warn('Config not loaded, cannot apply preset')
return
}
const preset = this.config.presets[presetName]
if (!preset) {
console.warn(`Preset '${presetName}' not found`)
return
}
// Deep merge preset into current config
this.config = this.deepMerge(this.config, preset)
console.log(`✅ Applied preset: ${presetName}`)
}
private deepMerge(target: any, source: any): any {
const result = { ...target }
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.deepMerge(target[key] || {}, source[key])
} else {
result[key] = source[key]
}
}
return result
}
getConfig(): VisualizerConfig | null {
return this.config
}
// Helper methods to get specific config sections
getParticleParams(sphereIndex: number): Partial<ParticleSystemParams> {
if (!this.config) return {}
const audioRange = this.config.audio.frequencyRanges[sphereIndex] || this.config.audio.frequencyRanges[0]
const colors = this.config.colors.sphereColorSchemes[sphereIndex] || this.config.colors.sphereColorSchemes[0]
return {
enabled: true,
particleCount: this.config.visualization.particles.defaultParticleCount,
particleSize: this.config.visualization.particles.particleSize,
particleLifetime: this.config.visualization.particles.particleLifetime,
sphereRadius: this.config.visualization.spheres.baseRadius + (sphereIndex * this.config.visualization.spheres.radiusIncrement),
innerSphereRadius: this.config.visualization.spheres.innerSphereRadius,
turbulenceStrength: this.config.visualization.physics.turbulenceStrength,
noiseScale: this.config.visualization.physics.noiseScale + (sphereIndex * this.config.visualization.physics.noiseScaleVariation),
noiseSpeed: this.config.visualization.physics.noiseSpeed,
rotationSpeedMin: this.config.visualization.rotation.rotationSpeedMin,
rotationSpeedMax: this.config.visualization.rotation.rotationSpeedMax + (sphereIndex * this.config.visualization.rotation.rotationSpeedIncrement),
rotationSmoothness: this.config.visualization.rotation.rotationSmoothness,
beatStrength: this.config.audio.beatDetection.beatStrength,
beatThreshold: audioRange.beatThreshold,
volumeChangeThreshold: audioRange.volumeChangeThreshold,
minFrequency: audioRange.minFrequency,
maxFrequency: audioRange.maxFrequency,
colorStart: colors.start,
colorEnd: colors.end,
dynamicNoiseScale: true
}
}
getCymateIntensity(): number {
return this.config?.cymatics.intensity || 0.002
}
getTendrilConfig(): any {
return this.config?.tendrils || {}
}
getEntronautConfig(): any {
return this.config?.entronaut || {}
}
getFogConfig(): any {
return this.config?.fog || {}
}
getCameraConfig(): any {
return this.config?.camera || {}
}
// Hot reload support for development
async reloadConfig(): Promise<VisualizerConfig> {
this.config = null
this.loadPromise = null
return this.loadConfig()
}
}
// Singleton instance
export const configLoader = new ConfigLoader()
// Export for use in components
export default configLoader