stemspleeter-com / index.html
cbwinslow's picture
Add 1 files
8b01e58 verified
<!DOCTYPE html>
<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">&times;</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>&copy; <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>