music-video-gen/party-cathedral/src/scene/music-player.js
2025-11-23 23:54:28 +01:00

129 lines
4.7 KiB
JavaScript

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();