import { state } from '../state.js'; import { SceneFeature } from './SceneFeature.js'; 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'); const metadataContainer = document.getElementById('metadata-container'); const songTitleElement = document.getElementById('song-title'); loadButton.addEventListener('click', () => { fileInput.click(); }); 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'; // Show metadata songTitleElement.textContent = file.name.replace(/\.[^/.]+$/, ""); // Show filename without extension metadataContainer.classList.remove('hidden'); const url = URL.createObjectURL(file); state.music.player.src = url; // Wait 5 seconds, then start the party setTimeout(() => { metadataContainer.classList.add('hidden'); this.startParty(); }, 5000); } }); state.music.player.addEventListener('ended', () => { this.stopParty(); uiContainer.style.display = 'flex'; // Show the button again }); } startParty() { state.clock.start(); state.music.player.play(); document.getElementById('ui-container').style.display = 'none'; state.partyStarted = true; // You could add BPM detection here in the future // For now, we use the fixed BPM // Trigger 'start' event for other features this.notifyFeatures('onPartyStart'); } stopParty() { state.clock.stop(); state.partyStarted = false; setTimeout(() => { const startButton = document.getElementById('loadMusicButton'); startButton.style.display = 'block'; startButton.textContent = "Party some more?" }, 5000); // Trigger 'end' event for other features this.notifyFeatures('onPartyEnd'); } notifyFeatures(methodName) { sceneFeatureManager.features.forEach(feature => { if (typeof feature[methodName] === 'function') { feature[methodName](); } }); } update(deltaTime) { 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; } } new MusicPlayer();