const mp3Files = document.getElementById('mp3Files'); const tapeLengthInput = document.getElementById('tapeLength'); const trackList = document.getElementById('trackList'); const totalDurationDisplay = document.getElementById('totalDuration'); const remainingTimeDisplay = document.getElementById('remainingTime'); const recordButton = document.getElementById('recordButton'); const progress = document.getElementById('progress'); const volumeIndicator = document.createElement('div'); volumeIndicator.className = 'volume-indicator'; document.body.appendChild(volumeIndicator); const reelImage = document.querySelector('.reel-spinner'); const STATIC_REEL = 'reel-to-reel-static.svg'; const ANIMATED_REEL = 'reel-to-reel-animated.svg'; const trackGapInput = document.getElementById('trackGap'); const body = document.body; let totalDuration = 0; let files = []; let audioContext; let analyser; mp3Files.addEventListener('change', handleFiles); tapeLengthInput.addEventListener('input', updateRemainingTime); recordButton.addEventListener('click', recordToTape); async function handleFiles(event) { const newFiles = Array.from(event.target.files); for (const file of newFiles) { files.push(file); const duration = await getDuration(file); totalDuration += duration; addTrackToPlaylist(file.name, duration, files.length - 1); } updateTotalDuration(); updateRemainingTime(); } async function getDuration(file) { return new Promise((resolve) => { const audio = new Audio(); audio.preload = 'metadata'; audio.src = URL.createObjectURL(file); audio.onloadedmetadata = () => { resolve(audio.duration / 60); // Duration in minutes URL.revokeObjectURL(audio.src); }; }); } function addTrackToPlaylist(name, duration, index) { const li = document.createElement('li'); li.draggable = true; li.dataset.index = index; li.textContent = `${name} (${duration.toFixed(2)} minutes)`; li.addEventListener('dragstart', handleDragStart); li.addEventListener('dragover', handleDragOver); li.addEventListener('drop', handleDrop); li.addEventListener('dragend', handleDragEnd); trackList.appendChild(li); } function updateTotalDuration() { totalDurationDisplay.textContent = totalDuration.toFixed(2); } function updateRemainingTime() { const tapeLength = parseFloat(tapeLengthInput.value); if (tapeLength) { const remainingTime = tapeLength - totalDuration; remainingTimeDisplay.textContent = remainingTime.toFixed(2); } } // ... other parts of the script async function recordToTape() { if (files.length === 0) { alert('Please add MP3 files to the playlist.'); return; } audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); analyser.fftSize = 256; analyser.connect(audioContext.destination); updateVolumeIndicator(); startReelAnimation(); body.classList.add('recording-active'); console.log("Starting playback with order:"); logPlaylist(); const gapSeconds = parseFloat(trackGapInput.value) || 0; try { for (let i = 0; i < files.length; i++) { console.log(`Playing track ${i}: ${files[i].name}`); const duration = await getDuration(files[i]); await playTrack(files[i], duration); if (i < files.length - 1 && gapSeconds > 0) { await addGapBetweenTracks(gapSeconds); } } } catch (error) { console.error("Playback error:", error); } stopReelAnimation(); body.classList.remove('recording-active'); alert('Recording completed!'); } async function playTrack(file, duration) { return new Promise((resolve) => { const audio = new Audio(); audio.src = URL.createObjectURL(file); const source = audioContext.createMediaElementSource(audio); source.connect(analyser); audio.currentTime = 0; // Always start from beginning audio.play(); audio.onended = () => { resolve(); URL.revokeObjectURL(audio.src); }; audio.ontimeupdate = () => { const currentPlayTime = audio.currentTime; const progressPercent = (currentPlayTime / (totalDuration * 60)) * 100; updateProgress(progressPercent); const tapeLength = parseFloat(tapeLengthInput.value) * 60; const remainingTime = tapeLength - currentPlayTime; remainingTimeDisplay.textContent = (remainingTime / 60).toFixed(2); }; }); } // ... rest of the script function updateProgress(percent) { progress.innerHTML = `
`; } function updateRemainingTimeDisplay(currentTime) { const tapeLength = parseFloat(tapeLengthInput.value) * 60; const remainingTime = tapeLength - currentTime; remainingTimeDisplay.textContent = (remainingTime / 60).toFixed(2); } function updateVolumeIndicator() { const dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(dataArray); const averageVolume = dataArray.reduce((a, b) => a + b) / dataArray.length; volumeIndicator.style.height = `${averageVolume / 255 * 100}px`; // Scale height based on volume requestAnimationFrame(updateVolumeIndicator); } function startReelAnimation() { reelImage.src = ANIMATED_REEL; } function stopReelAnimation() { reelImage.src = STATIC_REEL; } function addGapBetweenTracks(seconds) { return new Promise(resolve => { console.log(`Adding ${seconds} second gap`); const startTime = audioContext.currentTime; function checkGap() { const elapsed = audioContext.currentTime - startTime; if (elapsed >= seconds) { resolve(); } else { requestAnimationFrame(checkGap); } } checkGap(); }); } let draggedItem = null; function handleDragStart(e) { draggedItem = e.target; e.target.style.opacity = '0.4'; } function handleDragOver(e) { e.preventDefault(); const targetItem = e.target; // Only handle drag over list items if (targetItem.tagName === 'LI') { const bounding = targetItem.getBoundingClientRect(); const offset = bounding.y + (bounding.height/2); if (e.clientY - offset > 0) { targetItem.style.borderBottom = 'solid 2px #eed49f'; targetItem.style.borderTop = ''; } else { targetItem.style.borderTop = 'solid 2px #eed49f'; targetItem.style.borderBottom = ''; } } } function handleDrop(e) { e.preventDefault(); const targetItem = e.target; // Only handle drops on list items if (targetItem.tagName === 'LI' && draggedItem !== targetItem) { // Get the current order of all items const items = Array.from(trackList.children); const oldIndex = items.indexOf(draggedItem); // Remove and insert the dragged item draggedItem.parentNode.removeChild(draggedItem); // Determine if dropping before or after the target const bounding = targetItem.getBoundingClientRect(); const insertAfter = e.clientY > (bounding.top + bounding.height / 2); if (insertAfter) { targetItem.parentNode.insertBefore(draggedItem, targetItem.nextSibling); } else { targetItem.parentNode.insertBefore(draggedItem, targetItem); } // Get the new order of items const newItems = Array.from(trackList.children); const newIndex = newItems.indexOf(draggedItem); // Reorder the files array to match const [movedFile] = files.splice(oldIndex, 1); files.splice(newIndex, 0, movedFile); // Update all indices newItems.forEach((item, index) => { item.dataset.index = index; }); // Log the new order for debugging logPlaylist(); } clearDragOverStyles(); } function handleDragEnd(e) { e.target.style.opacity = ''; clearDragOverStyles(); } function clearDragOverStyles() { const items = trackList.querySelectorAll('li'); items.forEach(item => { item.style.borderTop = ''; item.style.borderBottom = ''; }); } function updatePlaylistIndices() { const items = trackList.querySelectorAll('li'); items.forEach((item, index) => { item.dataset.index = index; }); } // Add this function to help with debugging function logPlaylist() { console.log("Current playlist order:"); files.forEach((file, index) => { console.log(`${index}: ${file.name}`); }); }