Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>CRIMELAB357 - Professional Audio Equalizer</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #3578e5; | |
| --secondary-color: #2c3e50; | |
| --accent-color: #e74c3c; | |
| --dark-color: #1a1a1a; | |
| --light-color: #f5f5f5; | |
| --vu-green: #00ff00; | |
| --vu-yellow: #ffff00; | |
| --vu-red: #ff0000; | |
| --slider-track: #444; | |
| --slider-thumb: var(--primary-color); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background-color: var(--dark-color); | |
| color: var(--light-color); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-x: hidden; | |
| } | |
| header { | |
| background: linear-gradient(135deg, var(--secondary-color), var(--dark-color)); | |
| padding: 1rem; | |
| text-align: center; | |
| border-bottom: 2px solid var(--accent-color); | |
| position: relative; | |
| } | |
| .logo { | |
| font-size: 2.5rem; | |
| font-weight: bold; | |
| letter-spacing: 2px; | |
| color: var(--accent-color); | |
| text-shadow: 0 0 10px rgba(231, 76, 60, 0.5); | |
| margin-bottom: 0.5rem; | |
| } | |
| .tagline { | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| margin-bottom: 0.5rem; | |
| } | |
| .built-with { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| font-size: 0.8rem; | |
| color: var(--light-color); | |
| text-decoration: none; | |
| } | |
| .built-with:hover { | |
| text-decoration: underline; | |
| } | |
| main { | |
| flex: 1; | |
| padding: 1rem; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| width: 100%; | |
| } | |
| .upload-section { | |
| background-color: rgba(44, 62, 80, 0.3); | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| } | |
| .upload-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .file-input { | |
| display: none; | |
| } | |
| .upload-btn { | |
| background: linear-gradient(135deg, var(--primary-color), #4a6baf); | |
| color: white; | |
| border: none; | |
| padding: 0.8rem 1.5rem; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| box-shadow: 0 4px 10px rgba(53, 120, 229, 0.3); | |
| } | |
| .upload-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 15px rgba(53, 120, 229, 0.4); | |
| } | |
| .file-name { | |
| margin-top: 0.5rem; | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| } | |
| .player-controls { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .control-btn { | |
| background: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| color: var(--light-color); | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| font-size: 1.2rem; | |
| } | |
| .control-btn:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| transform: scale(1.05); | |
| } | |
| .control-btn.play { | |
| background: var(--primary-color); | |
| width: 60px; | |
| height: 60px; | |
| font-size: 1.5rem; | |
| } | |
| .control-btn.play:hover { | |
| background: #4a6baf; | |
| } | |
| .time-display { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| } | |
| .progress-container { | |
| width: 100%; | |
| height: 6px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| margin-bottom: 1.5rem; | |
| cursor: pointer; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--primary-color), #4a6baf); | |
| border-radius: 3px; | |
| width: 0%; | |
| position: relative; | |
| } | |
| .progress-bar::after { | |
| content: ''; | |
| position: absolute; | |
| right: -5px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 10px; | |
| height: 10px; | |
| background: white; | |
| border-radius: 50%; | |
| opacity: 0; | |
| transition: opacity 0.2s ease; | |
| } | |
| .progress-container:hover .progress-bar::after { | |
| opacity: 1; | |
| } | |
| .equalizer-container { | |
| background-color: rgba(44, 62, 80, 0.3); | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| } | |
| .eq-title { | |
| text-align: center; | |
| margin-bottom: 1.5rem; | |
| font-size: 1.2rem; | |
| color: var(--accent-color); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .eq-bands { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-end; | |
| height: 200px; | |
| gap: 5px; | |
| } | |
| .eq-band { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| flex: 1; | |
| height: 100%; | |
| } | |
| .eq-slider { | |
| -webkit-appearance: none; | |
| width: 30px; | |
| height: 150px; | |
| background: var(--slider-track); | |
| border-radius: 5px; | |
| outline: none; | |
| transform: rotate(180deg); | |
| margin-bottom: 0.5rem; | |
| cursor: pointer; | |
| } | |
| .eq-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 30px; | |
| height: 8px; | |
| background: var(--slider-thumb); | |
| border-radius: 3px; | |
| cursor: pointer; | |
| } | |
| .eq-slider::-moz-range-thumb { | |
| width: 30px; | |
| height: 8px; | |
| background: var(--slider-thumb); | |
| border-radius: 3px; | |
| cursor: pointer; | |
| } | |
| .eq-freq { | |
| font-size: 0.7rem; | |
| opacity: 0.7; | |
| writing-mode: vertical-rl; | |
| transform: rotate(180deg); | |
| margin-top: 0.5rem; | |
| } | |
| .analyzers { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .analyzer-box { | |
| flex: 1; | |
| min-width: 300px; | |
| background-color: rgba(44, 62, 80, 0.3); | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| } | |
| .analyzer-title { | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| font-size: 1.1rem; | |
| color: var(--accent-color); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .vu-meter { | |
| display: flex; | |
| height: 200px; | |
| gap: 2px; | |
| margin-top: 1rem; | |
| } | |
| .vu-channel { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column-reverse; | |
| gap: 2px; | |
| } | |
| .vu-segment { | |
| flex: 1; | |
| background: #333; | |
| border-radius: 2px; | |
| transition: background 0.1s ease; | |
| } | |
| .vu-segment.green { | |
| background: var(--vu-green); | |
| } | |
| .vu-segment.yellow { | |
| background: var(--vu-yellow); | |
| } | |
| .vu-segment.red { | |
| background: var(--vu-red); | |
| } | |
| .spectrum-analyzer { | |
| width: 100%; | |
| height: 200px; | |
| position: relative; | |
| margin-top: 1rem; | |
| } | |
| .spectrum-bars { | |
| display: flex; | |
| height: 100%; | |
| align-items: flex-end; | |
| gap: 2px; | |
| } | |
| .spectrum-bar { | |
| flex: 1; | |
| background: linear-gradient(to top, var(--primary-color), #4a6baf); | |
| border-radius: 2px; | |
| min-width: 2px; | |
| } | |
| .amplifier-control { | |
| background-color: rgba(44, 62, 80, 0.3); | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| } | |
| .amp-slider-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .amp-slider { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 8px; | |
| background: var(--slider-track); | |
| border-radius: 4px; | |
| outline: none; | |
| } | |
| .amp-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| background: var(--accent-color); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| } | |
| .amp-slider::-moz-range-thumb { | |
| width: 20px; | |
| height: 20px; | |
| background: var(--accent-color); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| } | |
| .amp-value { | |
| min-width: 40px; | |
| text-align: center; | |
| font-weight: bold; | |
| } | |
| .presets { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-top: 1rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .preset-btn { | |
| background: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| color: var(--light-color); | |
| padding: 0.5rem 1rem; | |
| border-radius: 20px; | |
| cursor: pointer; | |
| font-size: 0.8rem; | |
| transition: all 0.2s ease; | |
| } | |
| .preset-btn:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| } | |
| .preset-btn.active { | |
| background: var(--primary-color); | |
| font-weight: bold; | |
| } | |
| footer { | |
| text-align: center; | |
| padding: 1rem; | |
| font-size: 0.8rem; | |
| opacity: 0.7; | |
| background: rgba(0, 0, 0, 0.2); | |
| } | |
| @media (max-width: 768px) { | |
| .eq-bands { | |
| flex-wrap: wrap; | |
| height: auto; | |
| } | |
| .eq-band { | |
| width: calc(100% / 6 - 5px); | |
| height: 150px; | |
| margin-bottom: 1rem; | |
| } | |
| .eq-slider { | |
| height: 100px; | |
| } | |
| .analyzers { | |
| flex-direction: column; | |
| } | |
| .player-controls { | |
| gap: 0.5rem; | |
| } | |
| .control-btn { | |
| width: 40px; | |
| height: 40px; | |
| font-size: 1rem; | |
| } | |
| .control-btn.play { | |
| width: 50px; | |
| height: 50px; | |
| font-size: 1.2rem; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .eq-band { | |
| width: calc(100% / 4 - 5px); | |
| } | |
| .logo { | |
| font-size: 1.8rem; | |
| } | |
| .built-with { | |
| position: static; | |
| margin-top: 0.5rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="logo">CRIMELAB357</div> | |
| <div class="tagline">Professional Audio Processing Suite</div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="built-with">Built with anycoder</a> | |
| </header> | |
| <main> | |
| <section class="upload-section"> | |
| <div class="upload-container"> | |
| <input type="file" id="audio-upload" class="file-input" accept="audio/*"> | |
| <button class="upload-btn" id="upload-btn"> | |
| <i class="fas fa-upload"></i> Upload MP3 | |
| </button> | |
| <div class="file-name" id="file-name">No file selected</div> | |
| </div> | |
| </section> | |
| <section class="player-controls"> | |
| <button class="control-btn" id="prev-btn" title="Previous"> | |
| <i class="fas fa-step-backward"></i> | |
| </button> | |
| <button class="control-btn" id="play-btn" title="Play/Pause"> | |
| <i class="fas fa-play" id="play-icon"></i> | |
| </button> | |
| <button class="control-btn" id="next-btn" title="Next"> | |
| <i class="fas fa-step-forward"></i> | |
| </button> | |
| <button class="control-btn" id="stop-btn" title="Stop"> | |
| <i class="fas fa-stop"></i> | |
| </button> | |
| </section> | |
| <div class="time-display"> | |
| <span id="current-time">0:00</span> | |
| <span id="total-time">0:00</span> | |
| </div> | |
| <div class="progress-container" id="progress-container"> | |
| <div class="progress-bar" id="progress-bar"></div> | |
| </div> | |
| <section class="equalizer-container"> | |
| <h2 class="eq-title"> | |
| <i class="fas fa-sliders-h"></i> 24-Band Equalizer | |
| </h2> | |
| <div class="eq-bands" id="eq-bands"> | |
| <!-- EQ bands will be generated by JavaScript --> | |
| </div> | |
| </section> | |
| <section class="amplifier-control"> | |
| <h2 class="analyzer-title"> | |
| <i class="fas fa-volume-up"></i> Power Amplifier | |
| </h2> | |
| <div class="amp-slider-container"> | |
| <i class="fas fa-volume-down"></i> | |
| <input type="range" min="0" max="200" value="100" class="amp-slider" id="amp-slider"> | |
| <i class="fas fa-volume-up"></i> | |
| <span class="amp-value" id="amp-value">100%</span> | |
| </div> | |
| </section> | |
| <div class="analyzers"> | |
| <section class="analyzer-box"> | |
| <h2 class="analyzer-title"> | |
| <i class="fas fa-tachometer-alt"></i> VU Meters | |
| </h2> | |
| <div class="vu-meter" id="vu-meter"> | |
| <div class="vu-channel" id="vu-left"> | |
| <!-- VU segments will be generated by JavaScript --> | |
| </div> | |
| <div class="vu-channel" id="vu-right"> | |
| <!-- VU segments will be generated by JavaScript --> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="analyzer-box"> | |
| <h2 class="analyzer-title"> | |
| <i class="fas fa-chart-bar"></i> Spectrum Analyzer | |
| </h2> | |
| <div class="spectrum-analyzer"> | |
| <div class="spectrum-bars" id="spectrum-bars"> | |
| <!-- Spectrum bars will be generated by JavaScript --> | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| <section class="equalizer-container"> | |
| <h2 class="eq-title"> | |
| <i class="fas fa-prescription-bottle"></i> Presets | |
| </h2> | |
| <div class="presets"> | |
| <button class="preset-btn" data-preset="flat">Flat</button> | |
| <button class="preset-btn" data-preset="pop">Pop</button> | |
| <button class="preset-btn" data-preset="rock">Rock</button> | |
| <button class="preset-btn" data-preset="jazz">Jazz</button> | |
| <button class="preset-btn" data-preset="classical">Classical</button> | |
| <button class="preset-btn" data-preset="bass">Bass Boost</button> | |
| <button class="preset-btn" data-preset="treble">Treble Boost</button> | |
| <button class="preset-btn" data-preset="vocal">Vocal Boost</button> | |
| </div> | |
| </section> | |
| </main> | |
| <footer> | |
| CRIMELAB357 Audio Processing Suite © 2023 | Professional Audio Tools | |
| </footer> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Audio context and elements | |
| let audioContext; | |
| let audioElement = new Audio(); | |
| let audioSource; | |
| let analyser; | |
| let gainNode; | |
| let eqNodes = []; | |
| let isPlaying = false; | |
| let currentFile = null; | |
| // Frequency bands for 24-band EQ (30Hz to 24kHz) | |
| const eqFrequencies = [ | |
| 30, 40, 50, 63, 80, 100, 125, 160, 200, 250, | |
| 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, | |
| 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000, 24000 | |
| ].slice(0, 24); // Ensure we have exactly 24 bands | |
| // DOM elements | |
| const uploadBtn = document.getElementById('upload-btn'); | |
| const audioUpload = document.getElementById('audio-upload'); | |
| const fileName = document.getElementById('file-name'); | |
| const playBtn = document.getElementById('play-btn'); | |
| const playIcon = document.getElementById('play-icon'); | |
| const prevBtn = document.getElementById('prev-btn'); | |
| const nextBtn = document.getElementById('next-btn'); | |
| const stopBtn = document.getElementById('stop-btn'); | |
| const currentTimeEl = document.getElementById('current-time'); | |
| const totalTimeEl = document.getElementById('total-time'); | |
| const progressContainer = document.getElementById('progress-container'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const eqBandsContainer = document.getElementById('eq-bands'); | |
| const ampSlider = document.getElementById('amp-slider'); | |
| const ampValue = document.getElementById('amp-value'); | |
| const vuLeft = document.getElementById('vu-left'); | |
| const vuRight = document.getElementById('vu-right'); | |
| const spectrumBars = document.getElementById('spectrum-bars'); | |
| const presetButtons = document.querySelectorAll('.preset-btn'); | |
| // Initialize audio context on first user interaction | |
| function initAudioContext() { | |
| if (!audioContext) { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| setupAudioNodes(); | |
| } | |
| } | |
| // Setup audio nodes (analyser, gain, EQ) | |
| function setupAudioNodes() { | |
| if (!audioContext) return; | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 256; | |
| gainNode = audioContext.createGain(); | |
| gainNode.gain.value = 1.0; | |
| // Create EQ nodes | |
| eqNodes = []; | |
| for (let i = 0; i < eqFrequencies.length; i++) { | |
| const eqNode = audioContext.createBiquadFilter(); | |
| eqNode.type = "peaking"; | |
| eqNode.frequency.value = eqFrequencies[i]; | |
| eqNode.Q.value = 1.0; | |
| eqNode.gain.value = 0; | |
| eqNodes.push(eqNode); | |
| } | |
| // Connect nodes: source -> EQ chain -> gain -> analyser -> destination | |
| if (audioSource) { | |
| let lastNode = audioSource; | |
| // Connect through all EQ nodes | |
| for (const eqNode of eqNodes) { | |
| lastNode.connect(eqNode); | |
| lastNode = eqNode; | |
| } | |
| lastNode.connect(gainNode); | |
| gainNode.connect(analyser); | |
| analyser.connect(audioContext.destination); | |
| } | |
| } | |
| // Create EQ sliders | |
| function createEQBands() { | |
| eqBandsContainer.innerHTML = ''; | |
| for (let i = 0; i < eqFrequencies.length; i++) { | |
| const freq = eqFrequencies[i]; | |
| const freqLabel = freq >= 1000 ? (freq / 1000).toFixed(1) + 'k' : freq; | |
| const bandDiv = document.createElement('div'); | |
| bandDiv.className = 'eq-band'; | |
| const slider = document.createElement('input'); | |
| slider.type = 'range'; | |
| slider.className = 'eq-slider'; | |
| slider.min = '-12'; | |
| slider.max = '12'; | |
| slider.value = '0'; | |
| slider.step = '0.1'; | |
| slider.dataset.band = i; | |
| const freqLabelEl = document.createElement('div'); | |
| freqLabelEl.className = 'eq-freq'; | |
| freqLabelEl.textContent = freqLabel; | |
| slider.addEventListener('input', function() { | |
| if (eqNodes[i]) { | |
| eqNodes[i].gain.value = parseFloat(this.value); | |
| } | |
| }); | |
| bandDiv.appendChild(slider); | |
| bandDiv.appendChild(freqLabelEl); | |
| eqBandsContainer.appendChild(bandDiv); | |
| } | |
| } | |
| // Create VU meter segments | |
| function createVUMeters() { | |
| vuLeft.innerHTML = ''; | |
| vuRight.innerHTML = ''; | |
| // Create 30 segments (10 green, 10 yellow, 10 red) | |
| for (let i = 0; i < 30; i++) { | |
| const segmentLeft = document.createElement('div'); | |
| const segmentRight = document.createElement('div'); | |
| segmentLeft.className = 'vu-segment'; | |
| segmentRight.className = 'vu-segment'; | |
| if (i < 10) { | |
| segmentLeft.classList.add('green'); | |
| segmentRight.classList.add('green'); | |
| } else if (i < 20) { | |
| segmentLeft.classList.add('yellow'); | |
| segmentRight.classList.add('yellow'); | |
| } else { | |
| segmentLeft.classList.add('red'); | |
| segmentRight.classList.add('red'); | |
| } | |
| vuLeft.appendChild(segmentLeft); | |
| vuRight.appendChild(segmentRight); | |
| } | |
| } | |
| // Create spectrum analyzer bars | |
| function createSpectrumAnalyzer() { | |
| spectrumBars.innerHTML = ''; | |
| const barCount = 64; | |
| for (let i = 0; i < barCount; i++) { | |
| const bar = document.createElement('div'); | |
| bar.className = 'spectrum-bar'; | |
| spectrumBars.appendChild(bar); | |
| } | |
| } | |
| // Handle file upload | |
| uploadBtn.addEventListener('click', function() { | |
| audioUpload.click(); | |
| }); | |
| audioUpload.addEventListener('change', function(e) { | |
| if (e.target.files.length) { | |
| currentFile = e.target.files[0]; | |
| fileName.textContent = currentFile.name; | |
| const fileURL = URL.createObjectURL(currentFile); | |
| audioElement.src = fileURL; | |
| // Set up audio source when a file is selected | |
| if (audioContext) { | |
| if (audioSource) { | |
| audioSource.disconnect(); | |
| } | |
| audioSource = audioContext.createMediaElementSource(audioElement); | |
| setupAudioNodes(); | |
| } | |
| // Update total time display | |
| audioElement.addEventListener('loadedmetadata', function() { | |
| totalTimeEl.textContent = formatTime(audioElement.duration); | |
| }); | |
| } | |
| }); | |
| // Play/pause button | |
| playBtn.addEventListener('click', function() { | |
| initAudioContext(); | |
| if (audioElement.paused) { | |
| if (audioElement.src) { | |
| audioElement.play(); | |
| playIcon.className = 'fas fa-pause'; | |
| isPlaying = true; | |
| updateVisualizers(); | |
| } | |
| } else { | |
| audioElement.pause(); | |
| playIcon.className = 'fas fa-play'; | |
| isPlaying = false; | |
| } | |
| }); | |
| // Stop button | |
| stopBtn.addEventListener('click', function() { | |
| audioElement.pause(); | |
| audioElement.currentTime = 0; | |
| playIcon.className = 'fas fa-play'; | |
| isPlaying = false; | |
| }); | |
| // Previous button (placeholder) | |
| prevBtn.addEventListener('click', function() { | |
| // In a full player, this would go to previous track | |
| alert('Previous track functionality would be implemented in a full player'); | |
| }); | |
| // Next button (placeholder) | |
| nextBtn.addEventListener('click', function() { | |
| // In a full player, this would go to next track | |
| alert('Next track functionality would be implemented in a full player'); | |
| }); | |
| // Progress bar click | |
| progressContainer.addEventListener('click', function(e) { | |
| if (!audioElement.src) return; | |
| const rect = this.getBoundingClientRect(); | |
| const pos = (e.clientX - rect.left) / rect.width; | |
| audioElement.currentTime = pos * audioElement.duration; | |
| }); | |
| // Update progress bar and time display | |
| audioElement.addEventListener('timeupdate', function() { | |
| if (!isNaN(audioElement.duration)) { | |
| const progress = (audioElement.currentTime / audioElement.duration) * 100; | |
| progressBar.style.width = progress + '%'; | |
| currentTimeEl.textContent = formatTime(audioElement.currentTime); | |
| } | |
| }); | |
| // Audio ended | |
| audioElement.addEventListener('ended', function() { | |
| playIcon.className = 'fas fa-play'; | |
| isPlaying = false; | |
| progressBar.style.width = '0%'; | |
| currentTimeEl.textContent = '0:00'; | |
| }); | |
| // Amplifier control | |
| ampSlider.addEventListener('input', function() { | |
| const value = this.value; | |
| ampValue.textContent = value + '%'; | |
| if (gainNode) { | |
| gainNode.gain.value = value / 100; | |
| } | |
| }); | |
| // Format time (seconds to mm:ss) | |
| function formatTime(seconds) { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = Math.floor(seconds % 60); | |
| return mins + ':' + (secs < 10 ? '0' : '') + secs; | |
| } | |
| // Update visualizers (VU meters and spectrum analyzer) | |
| function updateVisualizers() { | |
| if (!isPlaying || !analyser) { | |
| requestAnimationFrame(updateVisualizers); | |
| return; | |
| } | |
| // VU Meter | |
| const vuData = new Uint8Array(2); | |
| analyser.getByteTimeDomainData(vuData); | |
| const leftSegments = vuLeft.querySelectorAll('.vu-segment'); | |
| const rightSegments = vuRight.querySelectorAll('.vu-segment'); | |
| // Convert to dB and scale for display | |
| const leftValue = Math.abs(vuData[0] - 128) / 128; | |
| const rightValue = Math.abs(vuData[1] - 128) / 128; | |
| const leftActive = Math.floor(leftValue * 30); | |
| const rightActive = Math.floor(rightValue * 30); | |
| leftSegments.forEach((seg, i) => { | |
| seg.style.opacity = i < leftActive ? '1' : '0.2'; | |
| }); | |
| rightSegments.forEach((seg, i) => { | |
| seg.style.opacity = i < rightActive ? '1' : '0.2'; | |
| }); | |
| // Spectrum Analyzer | |
| const freqData = new Uint8Array(analyser.frequencyBinCount); | |
| analyser.getByteFrequencyData(freqData); | |
| const bars = spectrumBars.querySelectorAll('.spectrum-bar'); | |
| const barCount = bars.length; | |
| for (let i = 0; i < barCount; i++) { | |
| const index = Math.floor(i * (freqData.length / barCount)); | |
| const value = freqData[index] / 255; | |
| const height = value * 100; | |
| bars[i].style.height = height + '%'; | |
| // Add some color variation | |
| const hue = 200 + (value * 60); | |
| bars[i].style.background = `linear-gradient(to top, hsl(${hue}, 100%, 50%), hsl(${hue}, 100%, 70%))`; | |
| } | |
| requestAnimationFrame(updateVisualizers); | |
| } | |
| // Preset buttons | |
| presetButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| const preset = this.dataset.preset; | |
| applyEQPreset(preset); | |
| // Update active state | |
| presetButtons.forEach(btn => btn.classList.remove('active')); | |
| this.classList.add('active'); | |
| }); | |
| }); | |
| // EQ presets | |
| function applyEQPreset(preset) { | |
| const sliders = eqBandsContainer.querySelectorAll('.eq-slider'); | |
| // Flat (all zero) | |
| const values = new Array(eqFrequencies.length).fill(0); | |
| switch(preset) { | |
| case 'pop': | |
| // Boost bass and treble | |
| values[0] = 4; values[1] = 3; values[2] = 2; // Low end | |
| values[values.length-3] = 2; values[values.length-2] = 3; values[values.length-1] = 4; // High end | |
| break; | |
| case 'rock': | |
| // Boost mid-highs | |
| for (let i = 8; i < 18; i++) values[i] = 2; | |
| values[4] = 3; values[5] = 3; // Some bass boost | |
| break; | |
| case 'jazz': | |
| // Gentle curve | |
| for (let i = 0; i < 6; i++) values[i] = 1; // Slight bass boost | |
| for (let i = 18; i < values.length; i++) values[i] = 1; // Slight treble boost | |
| break; | |
| case 'classical': | |
| // Boost highs, reduce lows | |
| for (let i = 0; i < 5; i++) values[i] = -1; | |
| for (let i = 15; i < values.length; i++) values[i] = 2; | |
| break; | |
| case 'bass': | |
| // Strong bass boost | |
| for (let i = 0; i < 8; i++) values[i] = 6; | |
| for (let i = 8; i < 12; i++) values[i] = -2; // Reduce low-mids to make bass clearer | |
| break; | |
| case 'treble': | |
| // Strong treble boost | |
| for (let i = values.length-8; i < values.length; i++) values[i] = 6; | |
| for (let i = 12; i < 18; i++) values[i] = -2; // Reduce mids to make highs clearer | |
| break; | |
| case 'vocal': | |
| // Boost vocal range (1kHz-4kHz) | |
| for (let i = 12; i < 18; i++) values[i] = 3; | |
| values[10] = -2; values[11] = -1; // Reduce low-mids | |
| values[19] = -1; values[20] = -2; // Reduce high-mids | |
| break; | |
| // 'flat' is already all zeros | |
| } | |
| // Apply values to sliders and EQ nodes | |
| for (let i = 0; i < sliders.length; i++) { | |
| sliders[i].value = values[i]; | |
| if (eqNodes[i]) { | |
| eqNodes[i].gain.value = values[i]; | |
| } | |
| } | |
| } | |
| // Initialize UI elements | |
| createEQBands(); | |
| createVUMeters(); | |
| createSpectrumAnalyzer(); | |
| // Apply flat preset by default | |
| applyEQPreset('flat'); | |
| presetButtons[0].classList.add('active'); | |
| }); | |
| </script> | |
| </body> | |
| </html> |