Feature: beat detection and visualization

This commit is contained in:
Dejvino 2025-11-23 22:06:53 +01:00
parent 451d4ea261
commit 6566416ebd
6 changed files with 57 additions and 11 deletions

View File

@ -176,7 +176,7 @@ export class Dancers extends SceneFeature {
}
} else {
const musicTime = state.clock.getElapsedTime();
if (state.music && state.music.beatIntensity > 0.8 && Math.random() < 0.5 && musicTime > 10) {
if (state.music && state.music.isLoudEnough && state.music.beatIntensity > 0.8 && Math.random() < 0.5 && musicTime > 10) {
dancerObj.isJumping = true;
dancerObj.jumpStartTime = time;
}

View File

@ -245,7 +245,7 @@ export class MedievalMusicians extends SceneFeature {
} else {
let currentJumpChance = jumpChance * deltaTime; // Base chance over time
const musicTime = state.clock.getElapsedTime();
if (state.music && state.music.beatIntensity > 0.8 && musicTime > 15) {
if (state.music && state.music.isLoudEnough && state.music.beatIntensity > 0.8 && musicTime > 15) {
currentJumpChance = 0.1; // High, fixed chance on the beat
}

View File

@ -1,4 +1,3 @@
import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
@ -6,11 +5,19 @@ import sceneFeatureManager from './SceneFeatureManager.js';
export class MusicPlayer extends SceneFeature {
constructor() {
super();
this.audioContext = null;
this.analyser = null;
this.source = null;
this.dataArray = null;
this.loudnessHistory = [];
sceneFeatureManager.register(this);
}
init() {
state.music.player = document.getElementById('audioPlayer');
state.music.loudness = 0;
state.music.isLoudEnough = false;
const loadButton = document.getElementById('loadMusicButton');
const fileInput = document.getElementById('musicFileInput');
const uiContainer = document.getElementById('ui-container');
@ -24,6 +31,17 @@ export class MusicPlayer extends SceneFeature {
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
// Setup Web Audio API if not already done
if (!this.audioContext) {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 128; // Lower resolution is fine for loudness
this.source = this.audioContext.createMediaElementSource(state.music.player);
this.source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
}
// Hide the main button
loadButton.style.display = 'none';
@ -84,8 +102,29 @@ export class MusicPlayer extends SceneFeature {
}
update(deltaTime) {
// The music player updates itself via events,
// but this could be used for real-time analysis in the future.
if (!state.partyStarted || !this.analyser) return;
this.analyser.getByteFrequencyData(this.dataArray);
// --- Calculate current loudness ---
let sum = 0;
for (let i = 0; i < this.dataArray.length; i++) {
sum += this.dataArray[i];
}
const average = sum / this.dataArray.length;
state.music.loudness = average / 255; // Normalize to 0-1 range
// --- Track loudness over the last 2 seconds ---
this.loudnessHistory.push(state.music.loudness);
if (this.loudnessHistory.length > 120) { // Assuming ~60fps, 2 seconds of history
this.loudnessHistory.shift();
}
// --- Determine if it's loud enough to jump ---
const avgLoudness = this.loudnessHistory.reduce((a, b) => a + b, 0) / this.loudnessHistory.length;
const quietThreshold = 0.1; // Adjust this value based on testing
state.music.isLoudEnough = avgLoudness > quietThreshold;
}
}

View File

@ -16,6 +16,7 @@ export class MusicVisualizer extends SceneFeature {
measureDuration: (60 / 120) * 4,
beatIntensity: 0,
measurePulse: 0,
isLoudEnough: false,
};
}

View File

@ -155,7 +155,7 @@ export class PartyGuests extends SceneFeature {
}
} else {
let currentJumpChance = jumpChance * deltaTime; // Base chance over time
if (state.music && state.music.beatIntensity > 0.8) {
if (state.music && state.music.isLoudEnough && state.music.beatIntensity > 0.8) {
currentJumpChance = 0.1; // High, fixed chance on the beat
}

View File

@ -58,7 +58,7 @@ export class StageTorches extends SceneFeature {
torchGroup.add(pointLight);
// --- Particle System for Fire ---
const particleCount = 50;
const particleCount = 100;
const particles = new THREE.BufferGeometry();
const positions = [];
const particleData = [];
@ -107,7 +107,10 @@ export class StageTorches extends SceneFeature {
this.torches.forEach(torch => {
let measurePulse = 0;
if (state.music) {
measurePulse = state.music.measurePulse * 4.0; // Make flames jump higher
measurePulse = state.music.measurePulse * 2.0; // Make flames jump higher
}
if (state.music.isLoudEnough) {
measurePulse += 2;
}
// --- Animate Particles ---
@ -119,11 +122,11 @@ export class StageTorches extends SceneFeature {
const yVelocity = data.velocity.y;
if (data.life <= 0 || positions[i * 3 + 1] < 0) {
// Reset particle
positions[i * 3] = 0;
positions[i * 3] = (Math.random() - 0.5) * 0.2;
positions[i * 3 + 1] = 1;
positions[i * 3 + 2] = 0;
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.2;
data.life = Math.random() * 1.0;
data.velocity.y = Math.random() * 1.5 + measurePulse;
data.velocity.y = Math.random() * 1.2 + measurePulse;
} else {
// Update position
positions[i * 3] += data.velocity.x * deltaTime;
@ -141,6 +144,9 @@ export class StageTorches extends SceneFeature {
let beatPulse = 0;
if (state.music) {
beatPulse = state.music.beatIntensity * 1.5;
if (state.music.isLoudEnough) {
beatPulse += 2;
}
}
torch.light.intensity = baseIntensity + flicker + beatPulse;