From 612a1bf501518aad69ab4f30451c41076a398534 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sat, 22 Nov 2025 00:04:01 +0100 Subject: [PATCH] Feature: music visualization --- party-cathedral/src/scene/light-ball.js | 9 +++++ party-cathedral/src/scene/music-visualizer.js | 39 +++++++++++++++++++ party-cathedral/src/scene/party-guests.js | 9 ++++- party-cathedral/src/scene/pillar-candles.js | 0 party-cathedral/src/scene/root.js | 1 + party-cathedral/src/scene/stage-torches.js | 18 +++++++-- .../src/scene/stained-glass-window.js | 11 ++++-- 7 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 party-cathedral/src/scene/music-visualizer.js create mode 100644 party-cathedral/src/scene/pillar-candles.js diff --git a/party-cathedral/src/scene/light-ball.js b/party-cathedral/src/scene/light-ball.js index 5b0ed77..fddc031 100644 --- a/party-cathedral/src/scene/light-ball.js +++ b/party-cathedral/src/scene/light-ball.js @@ -61,6 +61,15 @@ export class LightBall extends SceneFeature { mesh.position.y = 10 + Math.cos(time * driftSpeed * 1.3 + offset) * naveHeight/2 * 0.6; mesh.position.z = Math.cos(time * driftSpeed * 0.7 + offset) * length/2 * 0.8; light.position.copy(mesh.position); + + // --- Music Visualization --- + if (state.music) { + const baseIntensity = 4.0; + light.intensity = baseIntensity + state.music.beatIntensity * 3.0; + + const baseScale = 1.0; + mesh.scale.setScalar(baseScale + state.music.beatIntensity * 0.5); + } }); } } diff --git a/party-cathedral/src/scene/music-visualizer.js b/party-cathedral/src/scene/music-visualizer.js new file mode 100644 index 0000000..c359c56 --- /dev/null +++ b/party-cathedral/src/scene/music-visualizer.js @@ -0,0 +1,39 @@ +import { state } from '../state.js'; +import { SceneFeature } from './SceneFeature.js'; +import sceneFeatureManager from './SceneFeatureManager.js'; + +export class MusicVisualizer extends SceneFeature { + constructor() { + super(); + sceneFeatureManager.register(this); + } + + init() { + // Initialize music state + state.music = { + bpm: 120, + beatDuration: 60 / 120, + measureDuration: (60 / 120) * 4, + beatIntensity: 0, + measurePulse: 0, + }; + } + + update(deltaTime) { + if (!state.music) return; + + const time = state.clock.getElapsedTime(); + + // --- Calculate Beat Intensity (pulses every beat) --- + // This creates a sharp attack and slower decay (0 -> 1 -> 0) + const beatProgress = (time % state.music.beatDuration) / state.music.beatDuration; + state.music.beatIntensity = Math.pow(1.0 - beatProgress, 2); + + // --- Calculate Measure Pulse (spikes every 4 beats) --- + // This creates a very sharp spike for the torch flame effect + const measureProgress = (time % state.music.measureDuration) / state.music.measureDuration; + state.music.measurePulse = measureProgress < 0.2 ? Math.sin(measureProgress * Math.PI * 5) : 0; + } +} + +new MusicVisualizer(); \ No newline at end of file diff --git a/party-cathedral/src/scene/party-guests.js b/party-cathedral/src/scene/party-guests.js index e5ef18c..7e23c50 100644 --- a/party-cathedral/src/scene/party-guests.js +++ b/party-cathedral/src/scene/party-guests.js @@ -97,7 +97,7 @@ export class PartyGuests extends SceneFeature { const time = state.clock.getElapsedTime(); const moveSpeed = 1.0; // Move slower - const movementArea = { x: 10, z: 30, y: 0, centerZ: 0 }; + const movementArea = { x: 10, z: 30, y: 0, centerZ: 5 }; const jumpChance = 0.05; // Jump way more const jumpDuration = 0.5; const jumpHeight = 0.1; @@ -139,7 +139,12 @@ export class PartyGuests extends SceneFeature { mesh.position.y = movementArea.y + guestHeight / 2; } } else { - if (Math.random() < jumpChance) { + let currentJumpChance = jumpChance * deltaTime; // Base chance over time + if (state.music && state.music.beatIntensity > 0.8) { + currentJumpChance = 0.1; // High, fixed chance on the beat + } + + if (Math.random() < currentJumpChance) { guestObj.isJumping = true; guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance; guestObj.jumpStartTime = time; diff --git a/party-cathedral/src/scene/pillar-candles.js b/party-cathedral/src/scene/pillar-candles.js new file mode 100644 index 0000000..e69de29 diff --git a/party-cathedral/src/scene/root.js b/party-cathedral/src/scene/root.js index b659329..4f7718c 100644 --- a/party-cathedral/src/scene/root.js +++ b/party-cathedral/src/scene/root.js @@ -10,6 +10,7 @@ import { Stage } from './stage.js'; import { MedievalMusicians } from './medieval-musicians.js'; import { PartyGuests } from './party-guests.js'; import { StageTorches } from './stage-torches.js'; +import { MusicVisualizer } from './music-visualizer.js'; // Scene Features ^^^ // --- Scene Modeling Function --- diff --git a/party-cathedral/src/scene/stage-torches.js b/party-cathedral/src/scene/stage-torches.js index 0f28a8f..614bbdf 100644 --- a/party-cathedral/src/scene/stage-torches.js +++ b/party-cathedral/src/scene/stage-torches.js @@ -86,11 +86,17 @@ export class StageTorches extends SceneFeature { update(deltaTime) { this.torches.forEach(torch => { + let measurePulse = 0; + if (state.music) { + measurePulse = state.music.measurePulse * 2.0; // Make flames jump higher + } + // --- Animate Particles --- const positions = torch.particles.geometry.attributes.position.array; for (let i = 0; i < torch.particleData.length; i++) { const data = torch.particleData[i]; data.life -= deltaTime; + const yVelocity = data.velocity.y + measurePulse; if (data.life <= 0) { // Reset particle @@ -101,15 +107,21 @@ export class StageTorches extends SceneFeature { } else { // Update position positions[i * 3] += data.velocity.x * deltaTime; - positions[i * 3 + 1] += data.velocity.y * deltaTime; + positions[i * 3 + 1] += yVelocity * deltaTime; positions[i * 3 + 2] += data.velocity.z * deltaTime; } } torch.particles.geometry.attributes.position.needsUpdate = true; // --- Flicker Light --- - const flicker = Math.random() * 0.5; - torch.light.intensity = 2.0 + flicker; + const baseIntensity = 2.0; + const flicker = Math.random() * 0.6; + let beatPulse = 0; + if (state.music) { + beatPulse = state.music.beatIntensity * 1.5; + } + + torch.light.intensity = baseIntensity + flicker + beatPulse; }); } } diff --git a/party-cathedral/src/scene/stained-glass-window.js b/party-cathedral/src/scene/stained-glass-window.js index a9145c6..badb115 100644 --- a/party-cathedral/src/scene/stained-glass-window.js +++ b/party-cathedral/src/scene/stained-glass-window.js @@ -235,10 +235,13 @@ export class StainedGlass extends SceneFeature { update(deltaTime) { // Add a subtle pulsing glow to the windows - const pulseSpeed = 0.5; - const minIntensity = 0.1; // Increased intensity for a stronger glow - const maxIntensity = 0.2; - const intensity = minIntensity + (maxIntensity - minIntensity) * (0.5 * (1 + Math.sin(state.clock.getElapsedTime() * pulseSpeed))); + let intensity = 0.15; // Base intensity + + // --- Music Visualization --- + if (state.music) { + const beatPulse = state.music.beatIntensity * 0.3; + intensity += beatPulse; + } // To make the glow match the vertex colors, we set the emissive color to white // and modulate its intensity. The final glow color will be vertexColor * emissive * emissiveIntensity.