Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AudioStem Pro - Professional Audio Separation Tool</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #6a11cb; | |
| --secondary-color: #2575fc; | |
| --accent-color: #ff4e50; | |
| --light-color: #f8f9fa; | |
| --dark-color: #343a40; | |
| --success-color: #28a745; | |
| --warning-color: #ffc107; | |
| --danger-color: #dc3545; | |
| --border-radius: 12px; | |
| --box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| min-height: 100vh; | |
| color: var(--dark-color); | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| header { | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| padding: 1rem; | |
| background: linear-gradient(to right, var(--primary-color), var(--secondary-color)); | |
| color: white; | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .subtitle { | |
| font-size: 1.2rem; | |
| opacity: 0.9; | |
| } | |
| .upload-section { | |
| background-color: white; | |
| padding: 2rem; | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| margin-bottom: 2rem; | |
| } | |
| .upload-area { | |
| border: 3px dashed #ddd; | |
| border-radius: var(--border-radius); | |
| padding: 3rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| margin-bottom: 1rem; | |
| } | |
| .upload-area:hover { | |
| border-color: var(--secondary-color); | |
| background-color: rgba(37, 117, 252, 0.05); | |
| } | |
| .upload-area i { | |
| font-size: 4rem; | |
| color: var(--secondary-color); | |
| margin-bottom: 1rem; | |
| } | |
| .upload-area p { | |
| margin-bottom: 1rem; | |
| font-size: 1.1rem; | |
| } | |
| .file-input { | |
| display: none; | |
| } | |
| .controls { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .btn { | |
| padding: 0.8rem 1.5rem; | |
| border: none; | |
| border-radius: var(--border-radius); | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn-primary { | |
| background-color: var(--secondary-color); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| background-color: #1a65e0; | |
| transform: translateY(-2px); | |
| } | |
| .btn-secondary { | |
| background-color: white; | |
| color: var(--secondary-color); | |
| border: 2px solid var(--secondary-color); | |
| } | |
| .btn-secondary:hover { | |
| background-color: rgba(37, 117, 252, 0.1); | |
| } | |
| .btn-success { | |
| background-color: var(--success-color); | |
| color: white; | |
| } | |
| .btn-success:hover { | |
| background-color: #218838; | |
| transform: translateY(-2px); | |
| } | |
| .btn-danger { | |
| background-color: var(--danger-color); | |
| color: white; | |
| } | |
| .btn-danger:hover { | |
| background-color: #c82333; | |
| transform: translateY(-2px); | |
| } | |
| .progress-container { | |
| margin-top: 2rem; | |
| display: none; | |
| } | |
| .progress-bar { | |
| height: 10px; | |
| background-color: #e9ecef; | |
| border-radius: 5px; | |
| margin-bottom: 0.5rem; | |
| overflow: hidden; | |
| } | |
| .progress { | |
| height: 100%; | |
| background: linear-gradient(to right, var(--primary-color), var(--secondary-color)); | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| } | |
| .status-text { | |
| font-size: 0.9rem; | |
| color: #6c757d; | |
| } | |
| .results-section { | |
| display: none; | |
| background-color: white; | |
| padding: 2rem; | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| margin-bottom: 2rem; | |
| } | |
| .results-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .stems-container { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .stem-card { | |
| background-color: #f8f9fa; | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| transition: all 0.3s ease; | |
| } | |
| .stem-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| } | |
| .stem-icon { | |
| font-size: 2rem; | |
| margin-bottom: 1rem; | |
| color: var(--secondary-color); | |
| } | |
| .stem-name { | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| } | |
| .stem-controls { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-top: 1rem; | |
| } | |
| .stem-controls .btn { | |
| padding: 0.5rem 1rem; | |
| font-size: 0.9rem; | |
| } | |
| .playback-controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| margin-top: 1rem; | |
| } | |
| .playback-btn { | |
| background-color: transparent; | |
| border: none; | |
| font-size: 1.2rem; | |
| color: var(--secondary-color); | |
| cursor: pointer; | |
| } | |
| .volume-control { | |
| width: 100%; | |
| margin-top: 0.5rem; | |
| } | |
| .lyrics-section { | |
| display: none; | |
| background-color: white; | |
| padding: 2rem; | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| margin-bottom: 2rem; | |
| } | |
| .lyrics-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .lyrics-content { | |
| background-color: #f8f9fa; | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .lyrics-line { | |
| margin-bottom: 0.5rem; | |
| padding: 0.5rem; | |
| border-radius: 5px; | |
| } | |
| .lyrics-line:hover { | |
| background-color: #e9ecef; | |
| } | |
| .timestamp { | |
| color: var(--secondary-color); | |
| font-weight: 600; | |
| margin-right: 0.5rem; | |
| } | |
| .legal-section { | |
| background-color: white; | |
| padding: 2rem; | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--box-shadow); | |
| } | |
| .legal-section h2 { | |
| margin-bottom: 1rem; | |
| color: var(--primary-color); | |
| } | |
| .legal-section h3 { | |
| margin-top: 1.5rem; | |
| margin-bottom: 0.5rem; | |
| color: var(--secondary-color); | |
| } | |
| .legal-section p { | |
| margin-bottom: 1rem; | |
| font-size: 0.9rem; | |
| } | |
| footer { | |
| text-align: center; | |
| margin-top: 3rem; | |
| padding: 1rem; | |
| color: #6c757d; | |
| font-size: 0.9rem; | |
| } | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0,0,0,0.5); | |
| z-index: 1000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background-color: white; | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| max-width: 500px; | |
| width: 90%; | |
| max-height: 80vh; | |
| overflow-y: auto; | |
| } | |
| .modal-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .close-modal { | |
| background: none; | |
| border: none; | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| color: #6c757d; | |
| } | |
| .form-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| font-weight: 600; | |
| } | |
| .form-control { | |
| width: 100%; | |
| padding: 0.8rem; | |
| border: 1px solid #ced4da; | |
| border-radius: var(--border-radius); | |
| font-size: 1rem; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 1rem; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| } | |
| .btn { | |
| width: 100%; | |
| justify-content: center; | |
| } | |
| .stems-container { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| /* Waveform visualization */ | |
| .waveform-container { | |
| width: 100%; | |
| height: 100px; | |
| background-color: #f8f9fa; | |
| border-radius: 5px; | |
| margin-bottom: 1rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .waveform { | |
| display: flex; | |
| align-items: flex-end; | |
| height: 100%; | |
| position: absolute; | |
| bottom: 0; | |
| width: 100%; | |
| } | |
| .waveform-bar { | |
| flex: 1; | |
| height: 0%; | |
| background-color: var(--secondary-color); | |
| margin: 0 1px; | |
| min-width: 2px; | |
| border-radius: 2px 2px 0 0; | |
| transition: height 0.1s ease; | |
| } | |
| .playhead { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 2px; | |
| height: 100%; | |
| background-color: var(--accent-color); | |
| z-index: 10; | |
| } | |
| /* Loading animation */ | |
| .loader { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: 2rem 0; | |
| } | |
| .dot { | |
| width: 15px; | |
| height: 15px; | |
| background-color: var(--secondary-color); | |
| border-radius: 50%; | |
| margin: 0 5px; | |
| animation: bounce 1.5s infinite ease-in-out; | |
| } | |
| .dot:nth-child(1) { | |
| animation-delay: 0s; | |
| } | |
| .dot:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .dot:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes bounce { | |
| 0%, 100% { | |
| transform: translateY(0); | |
| } | |
| 50% { | |
| transform: translateY(-20px); | |
| } | |
| } | |
| /* Toggle switch */ | |
| .toggle-container { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| } | |
| .toggle { | |
| position: relative; | |
| display: inline-block; | |
| width: 60px; | |
| height: 30px; | |
| margin-right: 0.5rem; | |
| } | |
| .toggle input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .slider { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: #ccc; | |
| transition: .4s; | |
| border-radius: 34px; | |
| } | |
| .slider:before { | |
| position: absolute; | |
| content: ""; | |
| height: 22px; | |
| width: 22px; | |
| left: 4px; | |
| bottom: 4px; | |
| background-color: white; | |
| transition: .4s; | |
| border-radius: 50%; | |
| } | |
| input:checked + .slider { | |
| background-color: var(--secondary-color); | |
| } | |
| input:checked + .slider:before { | |
| transform: translateX(30px); | |
| } | |
| .toggle-label { | |
| font-weight: 500; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1><i class="fas fa-music"></i> AudioStem Pro</h1> | |
| <p class="subtitle">Separate audio stems and extract lyrics with professional quality</p> | |
| </header> | |
| <main> | |
| <section class="upload-section"> | |
| <div class="upload-area" id="dropArea"> | |
| <i class="fas fa-file-audio"></i> | |
| <p>Drag & drop your audio file here</p> | |
| <p>or</p> | |
| <button class="btn btn-primary" id="selectFileBtn">Select File</button> | |
| <input type="file" id="fileInput" class="file-input" accept="audio/*"> | |
| </div> | |
| <div class="toggle-container"> | |
| <label class="toggle"> | |
| <input type="checkbox" id="extractLyricsToggle" checked> | |
| <span class="slider"></span> | |
| </label> | |
| <span class="toggle-label">Extract Lyrics & Timestamps</span> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn btn-primary" id="processBtn" disabled> | |
| <i class="fas fa-cogs"></i> Process Audio | |
| </button> | |
| <button class="btn btn-secondary" id="resetBtn" disabled> | |
| <i class="fas fa-redo"></i> Reset | |
| </button> | |
| </div> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress-bar"> | |
| <div class="progress" id="progressBar"></div> | |
| </div> | |
| <p class="status-text" id="statusText">Processing audio... Please wait</p> | |
| </div> | |
| <audio id="audioPlayer" style="display: none;"></audio> | |
| </section> | |
| <section class="results-section" id="resultsSection"> | |
| <div class="results-header"> | |
| <h2><i class="fas fa-layer-group"></i> Separated Stems</h2> | |
| <button class="btn btn-success" id="downloadAllBtn"> | |
| <i class="fas fa-download"></i> Download All | |
| </button> | |
| </div> | |
| <div class="stems-container" id="stemsContainer"> | |
| <!-- Stem cards will be added dynamically --> | |
| </div> | |
| </section> | |
| <section class="lyrics-section" id="lyricsSection"> | |
| <div class="lyrics-header"> | |
| <h2><i class="fas fa-align-left"></i> Lyrics Transcription</h2> | |
| <button class="btn btn-success" id="downloadLyricsBtn"> | |
| <i class="fas fa-download"></i> Download Lyrics | |
| </button> | |
| </div> | |
| <div class="lyrics-content" id="lyricsContent"> | |
| <!-- Lyrics will be added dynamically --> | |
| </div> | |
| </section> | |
| <section class="legal-section"> | |
| <h2><i class="fas fa-balance-scale"></i> Legal Information</h2> | |
| <h3>Copyright Notice</h3> | |
| <p>AudioStem Pro is intended for educational and personal use only. Users are responsible for ensuring they have the necessary rights or permissions to process any audio file uploaded to this service.</p> | |
| <h3>Terms of Service</h3> | |
| <p>By using this service, you agree to the following terms:</p> | |
| <ul style="margin-left: 1.5rem; margin-bottom: 1rem;"> | |
| <li>You warrant that you own the rights to any audio file you upload or have obtained permission from the copyright holder.</li> | |
| <li>The service may not be used to circumvent copyright protection mechanisms or for unauthorized reproduction or distribution of copyrighted material.</li> | |
| <li>Any modified audio stems created by this service are derivative works subject to the original copyright terms.</li> | |
| <li>This service does not claim ownership over any processed files.</li> | |
| </ul> | |
| <h3>Fair Use Policy</h3> | |
| <p>This service adheres to the principles of fair use for copyrighted material. Acceptable uses include:</p> | |
| <ul style="margin-left: 1.5rem; margin-bottom: 1rem;"> | |
| <li>Non-commercial research and study</li> | |
| <li>Criticism or review</li> | |
| <li>Educational purposes</li> | |
| <li>Personal experimentation and learning</li> | |
| </ul> | |
| <h3>Prohibited Uses</h3> | |
| <p>You may not use this service to:</p> | |
| <ul style="margin-left: 1.5rem; margin-bottom: 1rem;"> | |
| <li>Create derivative works for commercial distribution without permission</li> | |
| <li>Claim ownership over modified versions of copyrighted works</li> | |
| <li>Violate any applicable copyright laws in your jurisdiction</li> | |
| <li>Process material you know to be infringing</li> | |
| </ul> | |
| <p style="font-style: italic;">The operators of this service reserve the right to refuse service to any user who violates these terms or appears to be using the service for copyright infringement.</p> | |
| </section> | |
| <div class="modal" id="renameModal"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h3>Rename Stem</h3> | |
| <button class="close-modal">×</button> | |
| </div> | |
| <div class="form-group"> | |
| <label for="newStemName">New Name</label> | |
| <input type="text" id="newStemName" class="form-control"> | |
| </div> | |
| <button class="btn btn-primary" id="confirmRenameBtn">Save Changes</button> | |
| </div> | |
| </div> | |
| </main> | |
| <footer> | |
| <p>© <span id="currentYear"></span> AudioStem Pro. All rights reserved.</p> | |
| </footer> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Set current year in footer | |
| document.getElementById('currentYear').textContent = new Date().getFullYear(); | |
| // DOM Elements | |
| const dropArea = document.getElementById('dropArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const selectFileBtn = document.getElementById('selectFileBtn'); | |
| const processBtn = document.getElementById('processBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const statusText = document.getElementById('statusText'); | |
| const resultsSection = document.getElementById('resultsSection'); | |
| const stemsContainer = document.getElementById('stemsContainer'); | |
| const lyricsSection = document.getElementById('lyricsSection'); | |
| const lyricsContent = document.getElementById('lyricsContent'); | |
| const downloadAllBtn = document.getElementById('downloadAllBtn'); | |
| const downloadLyricsBtn = document.getElementById('downloadLyricsBtn'); | |
| const audioPlayer = document.getElementById('audioPlayer'); | |
| const extractLyricsToggle = document.getElementById('extractLyricsToggle'); | |
| const renameModal = document.getElementById('renameModal'); | |
| const closeModalBtn = document.querySelector('.close-modal'); | |
| const newStemNameInput = document.getElementById('newStemName'); | |
| const confirmRenameBtn = document.getElementById('confirmRenameBtn'); | |
| // State variables | |
| let audioFile = null; | |
| let stems = []; | |
| let currentStemToRename = null; | |
| let lyrics = []; | |
| // Event listeners | |
| selectFileBtn.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFileSelect); | |
| dropArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropArea.style.borderColor = 'var(--secondary-color)'; | |
| dropArea.style.backgroundColor = 'rgba(37, 117, 252, 0.1)'; | |
| }); | |
| dropArea.addEventListener('dragleave', () => { | |
| dropArea.style.borderColor = '#ddd'; | |
| dropArea.style.backgroundColor = 'transparent'; | |
| }); | |
| dropArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropArea.style.borderColor = '#ddd'; | |
| dropArea.style.backgroundColor = 'transparent'; | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFileSelect({ target: fileInput }); | |
| } | |
| }); | |
| processBtn.addEventListener('click', processAudio); | |
| resetBtn.addEventListener('click', resetApp); | |
| downloadAllBtn.addEventListener('click', downloadAllStems); | |
| downloadLyricsBtn.addEventListener('click', downloadLyrics); | |
| closeModalBtn.addEventListener('click', () => renameModal.style.display = 'none'); | |
| confirmRenameBtn.addEventListener('click', renameStem); | |
| // Close modal when clicking outside | |
| window.addEventListener('click', (e) => { | |
| if (e.target === renameModal) { | |
| renameModal.style.display = 'none'; | |
| } | |
| }); | |
| // Functions | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; | |
| if (!file) return; | |
| // Check if file is an audio file | |
| if (!file.type.startsWith('audio/')) { | |
| alert('Please select an audio file.'); | |
| return; | |
| } | |
| audioFile = file; | |
| // Update UI | |
| dropArea.innerHTML = ` | |
| <i class="fas fa-check-circle" style="color: var(--success-color);"></i> | |
| <p>${file.name}</p> | |
| <p>${formatFileSize(file.size)} | ${file.type}</p> | |
| `; | |
| processBtn.disabled = false; | |
| resetBtn.disabled = false; | |
| } | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| function processAudio() { | |
| if (!audioFile) return; | |
| // Show progress | |
| progressContainer.style.display = 'block'; | |
| progressBar.style.width = '0%'; | |
| statusText.textContent = 'Processing audio... Please wait'; | |
| // Simulate processing (in a real app, this would call your backend API) | |
| simulateProcessing(); | |
| } | |
| function simulateProcessing() { | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 5; | |
| if (progress > 100) progress = 100; | |
| progressBar.style.width = `${progress}%`; | |
| if (progress < 30) { | |
| statusText.textContent = 'Analyzing audio...'; | |
| } else if (progress < 70) { | |
| statusText.textContent = 'Separating stems...'; | |
| } else if (progress < 90) { | |
| statusText.textContent = extractLyricsToggle.checked | |
| ? 'Detecting lyrics...' | |
| : 'Finalizing...'; | |
| } else { | |
| statusText.textContent = 'Almost done...'; | |
| } | |
| if (progress === 100) { | |
| clearInterval(interval); | |
| setTimeout(showResults, 500); | |
| } | |
| }, 300); | |
| } | |
| function showResults() { | |
| // Hide progress | |
| progressContainer.style.display = 'none'; | |
| // Create sample stems (in a real app, these would come from your processing) | |
| stems = [ | |
| { id: 1, name: 'Vocals', icon: 'fas fa-microphone', color: '#ff4e50', audioUrl: '#' }, | |
| { id: 2, name: 'Drums', icon: 'fas fa-drum', color: '#8e44ad', audioUrl: '#' }, | |
| { id: 3, name: 'Bass', icon: 'fas fa-guitar', color: '#3498db', audioUrl: '#' }, | |
| { id: 4, name: 'Other', icon: 'fas fa-sliders-h', color: '#2ecc71', audioUrl: '#' } | |
| ]; | |
| // Create sample lyrics if enabled | |
| if (extractLyricsToggle.checked) { | |
| lyrics = generateSampleLyrics(); | |
| } | |
| // Display stems | |
| displayStems(); | |
| // Display lyrics if extracted | |
| if (lyrics.length > 0) { | |
| displayLyrics(); | |
| lyricsSection.style.display = 'block'; | |
| } | |
| // Show results section | |
| resultsSection.style.display = 'block'; | |
| // Hide upload section | |
| document.querySelector('.upload-section').style.display = 'none'; | |
| } | |
| function displayStems() { | |
| stemsContainer.innerHTML = ''; | |
| stems.forEach(stem => { | |
| const stemCard = document.createElement('div'); | |
| stemCard.className = 'stem-card'; | |
| stemCard.innerHTML = ` | |
| <div class="stem-icon" style="color: ${stem.color}"> | |
| <i class="${stem.icon}"></i> | |
| </div> | |
| <h3 class="stem-name">${stem.name}</h3> | |
| <div class="waveform-container"> | |
| <div class="waveform" id="waveform-${stem.id}"> | |
| ${Array(50).fill().map(() => | |
| `<div class="waveform-bar" style="height:${Math.random() * 80 + 10}%"></div>` | |
| ).join('')} | |
| </div> | |
| <div class="playhead"></div> | |
| </div> | |
| <div class="playback-controls"> | |
| <button class="playback-btn play-stem" data-stem-id="${stem.id}"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| <input type="range" class="volume-control" min="0" max="1" step="0.01" value="0.7"> | |
| </div> | |
| <div class="stem-controls"> | |
| <button class="btn btn-secondary download-stem" data-stem-id="${stem.id}"> | |
| <i class="fas fa-download"></i> Download | |
| </button> | |
| <button class="btn btn-secondary rename-stem" data-stem-id="${stem.id}"> | |
| <i class="fas fa-edit"></i> Rename | |
| </button> | |
| </div> | |
| `; | |
| stemsContainer.appendChild(stemCard); | |
| }); | |
| // Add event listeners for playback controls | |
| document.querySelectorAll('.play-stem').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const stemId = this.getAttribute('data-stem-id'); | |
| playStem(stemId); | |
| }); | |
| }); | |
| // Add event listeners for volume controls | |
| document.querySelectorAll('.volume-control').forEach(slider => { | |
| slider.addEventListener('input', function() { | |
| // In a real app, this would adjust the volume of the playing audio | |
| }); | |
| }); | |
| // Add event listeners for download buttons | |
| document.querySelectorAll('.download-stem').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const stemId = this.getAttribute('data-stem-id'); | |
| downloadStem(stemId); | |
| }); | |
| }); | |
| // Add event listeners for rename buttons | |
| document.querySelectorAll('.rename-stem').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const stemId = this.getAttribute('data-stem-id'); | |
| openRenameModal(stemId); | |
| }); | |
| }); | |
| } | |
| function playStem(stemId) { | |
| // In a real app, this would play the actual stem audio | |
| alert(`Playing stem ${stemId} - This would play the audio in a real implementation`); | |
| // Visual feedback | |
| const playButton = document.querySelector(`.play-stem[data-stem-id="${stemId}"]`); | |
| const icon = playButton.querySelector('i'); | |
| if (icon.classList.contains('fa-play')) { | |
| icon.classList.remove('fa-play'); | |
| icon.classList.add('fa-pause'); | |
| // Animate waveform | |
| const waveform = document.getElementById(`waveform-${stemId}`); | |
| const bars = waveform.querySelectorAll('.waveform-bar'); | |
| const interval = setInterval(() => { | |
| bars.forEach(bar => { | |
| let newHeight = Math.random() * 80 + 10; | |
| bar.style.height = `${newHeight}%`; | |
| }); | |
| }, 100); | |
| // Save interval ID on the button for later clearing | |
| playButton.dataset.intervalId = interval; | |
| } else { | |
| icon.classList.remove('fa-pause'); | |
| icon.classList.add('fa-play'); | |
| // Stop waveform animation | |
| clearInterval(playButton.dataset.intervalId); | |
| } | |
| } | |
| function downloadStem(stemId) { | |
| const stem = stems.find(s => s.id == stemId); | |
| if (!stem) return; | |
| // In a real app, this would download the actual stem file | |
| alert(`Downloading ${stem.name}.mp3 - This would download the actual file in a real implementation`); | |
| // Create a fake download (for demo purposes) | |
| const a = document.createElement('a'); | |
| a.href = `data:text/plain;charset=utf-8,${encodeURIComponent(`This would be the ${stem.name} stem in a real implementation.`)}`; | |
| a.download = `${stem.name}.txt`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| } | |
| function openRenameModal(stemId) { | |
| const stem = stems.find(s => s.id == stemId); | |
| if (!stem) return; | |
| currentStemToRename = stem; | |
| newStemNameInput.value = stem.name; | |
| renameModal.style.display = 'flex'; | |
| } | |
| function renameStem() { | |
| if (!currentStemToRename) return; | |
| const newName = newStemNameInput.value.trim(); | |
| if (!newName) { | |
| alert('Please enter a valid name'); | |
| return; | |
| } | |
| currentStemToRename.name = newName; | |
| displayStems(); // Refresh the display | |
| renameModal.style.display = 'none'; | |
| } | |
| function downloadAllStems() { | |
| // In a real app, this would zip all stems and download | |
| alert('This would download a zip file containing all stems in a real implementation'); | |
| // Create a fake download (for demo purposes) | |
| const a = document.createElement('a'); | |
| a.href = `data:text/plain;charset=utf-8,${encodeURIComponent('This would be a zip file containing all stems in a real implementation.')}`; | |
| a.download = 'all_stems.zip'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| } | |
| function generateSampleLyrics() { | |
| return [ | |
| { time: '00:00:12', text: 'I heard there was a secret chord' }, | |
| { time: '00:00:18', text: 'That David played and it pleased the Lord' }, | |
| { time: '00:00:24', text: 'But you don\'t really care for music, do you?' }, | |
| { time: '00:00:31', text: 'Well it goes like this' }, | |
| { time: '00:00:34', text: 'The fourth, the fifth' }, | |
| { time: '00:00:37', text: 'The minor fall and the major lift' }, | |
| { time: '00:00:43', text: 'The baffled king composing Hallelujah' } | |
| ]; | |
| } | |
| function displayLyrics() { | |
| lyricsContent.innerHTML = ''; | |
| lyrics.forEach(line => { | |
| const lineElement = document.createElement('div'); | |
| lineElement.className = 'lyrics-line'; | |
| lineElement.innerHTML = ` | |
| <span class="timestamp">[${line.time}]</span> | |
| ${line.text} | |
| `; | |
| // Add click event to seek to that time | |
| lineElement.addEventListener('click', () => { | |
| alert(`Seeking to ${line.time} - This would seek the audio player in a real implementation`); | |
| }); | |
| lyricsContent.appendChild(lineElement); | |
| }); | |
| } | |
| function downloadLyrics() { | |
| if (lyrics.length === 0) return; | |
| // Format lyrics as text with timestamps | |
| let lyricsText = 'LYRICS\n=======\n\n'; | |
| lyrics.forEach(line => { | |
| lyricsText += `${line.time} ${line.text}\n`; | |
| }); | |
| // Create download link | |
| const a = document.createElement('a'); | |
| a.href = `data:text/plain;charset=utf-8,${encodeURIComponent(lyricsText)}`; | |
| a.download = 'lyrics.txt'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| } | |
| function resetApp() { | |
| audioFile = null; | |
| stems = []; | |
| lyrics = []; | |
| // Reset UI | |
| dropArea.innerHTML = ` | |
| <i class="fas fa-file-audio"></i> | |
| <p>Drag & drop your audio file here</p> | |
| <p>or</p> | |
| <button class="btn btn-primary" id="selectFileBtn">Select File</button> | |
| `; | |
| fileInput.value = ''; | |
| progressContainer.style.display = 'none'; | |
| resultsSection.style.display = 'none'; | |
| lyricsSection.style.display = 'none'; | |
| processBtn.disabled = true; | |
| resetBtn.disabled = true; | |
| // Show upload section if hidden | |
| document.querySelector('.upload-section').style.display = 'block'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |