// Neon Wave Player - JavaScript Module class NeonWavePlayer { constructor() { // Audio elements this.audio = document.getElementById('audio-player'); this.canvas = document.getElementById('waveform-visualizer'); this.ctx = this.canvas.getContext('2d'); // Audio context for visualizer this.audioContext = null; this.analyser = null; this.source = null; this.dataArray = null; // Player state this.isPlaying = false; this.currentTrackIndex = 0; this.volume = 0.7; this.shuffleMode = false; this.repeatMode = 'off'; // off, one, all // Playlist data this.playlist = [ { title: 'Digital Dreams', artist: 'Neon Collective', duration: '3:45', genre: 'Synthwave', year: '2089', bpm: '128', url: 'https://example.com/track1.mp3' }, { title: 'Chrome Hearts', artist: 'Cyber Punk', duration: '4:12', genre: 'Dark Synth', year: '2090', bpm: '140', url: 'https://example.com/track2.mp3' }, { title: 'Neural Network', artist: 'AI Composer', duration: '5:23', genre: 'Ambient', year: '2091', bpm: '90', url: 'https://example.com/track3.mp3' }, { title: 'Hologram Highway', artist: 'Future Bass', duration: '3:58', genre: 'Electronic', year: '2088', bpm: '150', url: 'https://example.com/track4.mp3' }, { title: 'Quantum Flux', artist: 'Void Walker', duration: '6:30', genre: 'Experimental', year: '2092', bpm: '175', url: 'https://example.com/track5.mp3' } ]; this.init(); } init() { this.setupEventListeners(); this.renderPlaylist(); this.loadTrack(0); this.setupAudioContext(); this.resizeCanvas(); // Start animation loop this.animate(); } setupAudioContext() { try { this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); this.analyser = this.audioContext.createAnalyser(); this.analyser.fftSize = 256; this.analyser.smoothingTimeConstant = 0.8; const bufferLength = this.analyser.frequencyBinCount; this.dataArray = new Uint8Array(bufferLength); // Connect audio element to analyser this.source = this.audioContext.createMediaElementSource(this.audio); this.source.connect(this.analyser); this.analyser.connect(this.audioContext.destination); } catch (error) { console.error('Failed to setup audio context:', error); } } setupEventListeners() { // Play/Pause document.getElementById('play-pause-btn').addEventListener('click', () => this.togglePlayPause()); // Navigation document.getElementById('prev-btn').addEventListener('click', () => this.previousTrack()); document.getElementById('next-btn').addEventListener('click', () => this.nextTrack()); // Modes document.getElementById('shuffle-btn').addEventListener('click', () => this.toggleShuffle()); document.getElementById('repeat-btn').addEventListener('click', () => this.toggleRepeat()); // Volume const volumeSlider = document.getElementById('volume-slider'); volumeSlider.addEventListener('input', (e) => this.setVolume(e.target.value / 100)); // Seek const seekSlider = document.getElementById('seek-slider'); seekSlider.addEventListener('input', (e) => this.seek(e.target.value)); // Audio events this.audio.addEventListener('timeupdate', () => this.updateProgress()); this.audio.addEventListener('ended', () => this.handleTrackEnd()); this.audio.addEventListener('loadedmetadata', () => this.updateDuration()); // EQ presets document.querySelectorAll('.eq-preset').forEach(btn => { btn.addEventListener('click', (e) => this.setEQPreset(e.target.dataset.preset)); }); // Add track button document.getElementById('add-track-btn').addEventListener('click', () => this.addTrack()); // Window resize window.addEventListener('resize', () => this.resizeCanvas()); } resizeCanvas() { const container = this.canvas.parentElement; this.canvas.width = container.offsetWidth; this.canvas.height = 200; } loadTrack(index) { const track = this.playlist[index]; if (!track) return; this.currentTrackIndex = index; // Update UI document.getElementById('track-title').textContent = track.title; document.getElementById('track-artist').textContent = track.artist; document.getElementById('track-genre').textContent = track.genre; document.getElementById('track-year').textContent = track.year; document.getElementById('track-bpm').textContent = track.bpm + ' BPM'; // Update playlist highlighting this.updatePlaylistHighlight(); // Load audio (using placeholder URL) // In real implementation, this would load actual audio files // this.audio.src = track.url; // Reset progress document.getElementById('progress-fill').style.width = '0%'; document.getElementById('current-time').textContent = '00:00'; document.getElementById('duration-time').textContent = track.duration; } togglePlayPause() { if (this.audioContext && this.audioContext.state === 'suspended') { this.audioContext.resume(); } if (this.isPlaying) { this.pause(); } else { this.play(); } } play() { // In real implementation, this would play the audio // this.audio.play(); this.isPlaying = true; document.getElementById('play-icon').classList.add('hidden'); document.getElementById('pause-icon').classList.remove('hidden'); // Simulate playback this.simulatePlayback(); } pause() { // In real implementation, this would pause the audio // this.audio.pause(); this.isPlaying = false; document.getElementById('play-icon').classList.remove('hidden'); document.getElementById('pause-icon').classList.add('hidden'); // Stop simulation if (this.playbackInterval) { clearInterval(this.playbackInterval); } } simulatePlayback() { let progress = 0; this.playbackInterval = setInterval(() => { if (progress >= 100) { this.handleTrackEnd(); return; } progress += 0.5; document.getElementById('progress-fill').style.width = progress + '%'; document.getElementById('seek-slider').value = progress; // Update time display const duration = this.parseDuration(this.playlist[this.currentTrackIndex].duration); const current = (progress / 100) * duration; document.getElementById('current-time').textContent = this.formatTime(current); }, 100); } parseDuration(durationStr) { const [minutes, seconds] = durationStr.split(':').map(Number); return minutes * 60 + seconds; } formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } previousTrack() { if (this.shuffleMode) { this.playRandomTrack(); } else { this.currentTrackIndex = (this.currentTrackIndex - 1 + this.playlist.length) % this.playlist.length; this.loadTrack(this.currentTrackIndex); if (this.isPlaying) this.play(); } } nextTrack() { if (this.shuffleMode) { this.playRandomTrack(); } else { this.currentTrackIndex = (this.currentTrackIndex + 1) % this.playlist.length; this.loadTrack(this.currentTrackIndex); if (this.isPlaying) this.play(); } } playRandomTrack() { let newIndex; do { newIndex = Math.floor(Math.random() * this.playlist.length); } while (newIndex === this.currentTrackIndex && this.playlist.length > 1); this.loadTrack(newIndex); if (this.isPlaying) this.play(); } handleTrackEnd() { if (this.repeatMode === 'one') { this.seek(0); this.play(); } else if (this.repeatMode === 'all' || this.currentTrackIndex < this.playlist.length - 1) { this.nextTrack(); } else { this.pause(); } } toggleShuffle() { this.shuffleMode = !this.shuffleMode; const shuffleBtn = document.getElementById('shuffle-btn'); shuffleBtn.dataset.active = this.shuffleMode; } toggleRepeat() { const modes = ['off', 'all', 'one']; const currentIndex = modes.indexOf(this.repeatMode); this.repeatMode = modes[(currentIndex + 1) % modes.length]; const repeatBtn = document.getElementById('repeat-btn'); repeatBtn.dataset.mode = this.repeatMode; // Update visual indicator if (this.repeatMode === 'off') { repeatBtn.style.color = ''; } else if (this.repeatMode === 'all') { repeatBtn.style.color = 'var(--neon-cyan)'; } else { repeatBtn.style.color = 'var(--neon-pink)'; } } setVolume(value) { this.volume = value; this.audio.volume = value; document.getElementById('volume-level').textContent = Math.round(value * 100) + '%'; // Update volume icon const volumeBtn = document.getElementById('volume-btn'); if (value === 0) { volumeBtn.innerHTML = ''; } else if (value < 0.5) { volumeBtn.innerHTML = ''; } else { volumeBtn.innerHTML = ''; } } seek(value) { const duration = this.parseDuration(this.playlist[this.currentTrackIndex].duration); const seekTime = (value / 100) * duration; // In real implementation, this would seek the audio // this.audio.currentTime = seekTime; document.getElementById('progress-fill').style.width = value + '%'; document.getElementById('current-time').textContent = this.formatTime(seekTime); } updateProgress() { const progress = (this.audio.currentTime / this.audio.duration) * 100; document.getElementById('progress-fill').style.width = progress + '%'; document.getElementById('seek-slider').value = progress; document.getElementById('current-time').textContent = this.formatTime(this.audio.currentTime); } updateDuration() { document.getElementById('duration-time').textContent = this.formatTime(this.audio.duration); } setEQPreset(preset) { // Update active state document.querySelectorAll('.eq-preset').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-preset="${preset}"]`).classList.add('active'); // In real implementation, this would apply audio filters console.log(`EQ preset set to: ${preset}`); } renderPlaylist() { const playlistElement = document.getElementById('playlist'); playlistElement.innerHTML = ''; this.playlist.forEach((track, index) => { const li = document.createElement('li'); li.className = 'playlist-item'; li.innerHTML = ` ${(index + 1).toString().padStart(2, '0')}