script.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. const mp3Files = document.getElementById('mp3Files');
  2. const tapeLengthInput = document.getElementById('tapeLength');
  3. const trackList = document.getElementById('trackList');
  4. const totalDurationDisplay = document.getElementById('totalDuration');
  5. const remainingTimeDisplay = document.getElementById('remainingTime');
  6. const recordButton = document.getElementById('recordButton');
  7. const progress = document.getElementById('progress');
  8. const volumeIndicator = document.createElement('div');
  9. volumeIndicator.className = 'volume-indicator';
  10. document.body.appendChild(volumeIndicator);
  11. const reelImage = document.querySelector('.reel-spinner');
  12. const STATIC_REEL = 'reel-to-reel-static.svg';
  13. const ANIMATED_REEL = 'reel-to-reel-animated.svg';
  14. const trackGapInput = document.getElementById('trackGap');
  15. const body = document.body;
  16. let totalDuration = 0;
  17. let files = [];
  18. let audioContext;
  19. let analyser;
  20. mp3Files.addEventListener('change', handleFiles);
  21. tapeLengthInput.addEventListener('input', updateRemainingTime);
  22. recordButton.addEventListener('click', recordToTape);
  23. async function handleFiles(event) {
  24. const newFiles = Array.from(event.target.files);
  25. for (const file of newFiles) {
  26. files.push(file);
  27. const duration = await getDuration(file);
  28. totalDuration += duration;
  29. addTrackToPlaylist(file.name, duration, files.length - 1);
  30. }
  31. updateTotalDuration();
  32. updateRemainingTime();
  33. }
  34. async function getDuration(file) {
  35. return new Promise((resolve) => {
  36. const audio = new Audio();
  37. audio.preload = 'metadata';
  38. audio.src = URL.createObjectURL(file);
  39. audio.onloadedmetadata = () => {
  40. resolve(audio.duration / 60); // Duration in minutes
  41. URL.revokeObjectURL(audio.src);
  42. };
  43. });
  44. }
  45. function addTrackToPlaylist(name, duration, index) {
  46. const li = document.createElement('li');
  47. li.draggable = true;
  48. li.dataset.index = index;
  49. li.textContent = `${name} (${duration.toFixed(2)} minutes)`;
  50. li.addEventListener('dragstart', handleDragStart);
  51. li.addEventListener('dragover', handleDragOver);
  52. li.addEventListener('drop', handleDrop);
  53. li.addEventListener('dragend', handleDragEnd);
  54. trackList.appendChild(li);
  55. }
  56. function updateTotalDuration() {
  57. totalDurationDisplay.textContent = totalDuration.toFixed(2);
  58. }
  59. function updateRemainingTime() {
  60. const tapeLength = parseFloat(tapeLengthInput.value);
  61. if (tapeLength) {
  62. const remainingTime = tapeLength - totalDuration;
  63. remainingTimeDisplay.textContent = remainingTime.toFixed(2);
  64. }
  65. }
  66. // ... other parts of the script
  67. async function recordToTape() {
  68. if (files.length === 0) {
  69. alert('Please add MP3 files to the playlist.');
  70. return;
  71. }
  72. audioContext = new (window.AudioContext || window.webkitAudioContext)();
  73. analyser = audioContext.createAnalyser();
  74. analyser.fftSize = 256;
  75. analyser.connect(audioContext.destination);
  76. updateVolumeIndicator();
  77. startReelAnimation();
  78. body.classList.add('recording-active');
  79. console.log("Starting playback with order:");
  80. logPlaylist();
  81. const gapSeconds = parseFloat(trackGapInput.value) || 0;
  82. try {
  83. for (let i = 0; i < files.length; i++) {
  84. console.log(`Playing track ${i}: ${files[i].name}`);
  85. const duration = await getDuration(files[i]);
  86. await playTrack(files[i], duration);
  87. if (i < files.length - 1 && gapSeconds > 0) {
  88. await addGapBetweenTracks(gapSeconds);
  89. }
  90. }
  91. } catch (error) {
  92. console.error("Playback error:", error);
  93. }
  94. stopReelAnimation();
  95. body.classList.remove('recording-active');
  96. alert('Recording completed!');
  97. }
  98. async function playTrack(file, duration) {
  99. return new Promise((resolve) => {
  100. const audio = new Audio();
  101. audio.src = URL.createObjectURL(file);
  102. const source = audioContext.createMediaElementSource(audio);
  103. source.connect(analyser);
  104. audio.currentTime = 0; // Always start from beginning
  105. audio.play();
  106. audio.onended = () => {
  107. resolve();
  108. URL.revokeObjectURL(audio.src);
  109. };
  110. audio.ontimeupdate = () => {
  111. const currentPlayTime = audio.currentTime;
  112. const progressPercent = (currentPlayTime / (totalDuration * 60)) * 100;
  113. updateProgress(progressPercent);
  114. const tapeLength = parseFloat(tapeLengthInput.value) * 60;
  115. const remainingTime = tapeLength - currentPlayTime;
  116. remainingTimeDisplay.textContent = (remainingTime / 60).toFixed(2);
  117. };
  118. });
  119. }
  120. // ... rest of the script
  121. function updateProgress(percent) {
  122. progress.innerHTML = `<div class="progress-bar" style="width: ${percent}%;"></div>`;
  123. }
  124. function updateRemainingTimeDisplay(currentTime) {
  125. const tapeLength = parseFloat(tapeLengthInput.value) * 60;
  126. const remainingTime = tapeLength - currentTime;
  127. remainingTimeDisplay.textContent = (remainingTime / 60).toFixed(2);
  128. }
  129. function updateVolumeIndicator() {
  130. const dataArray = new Uint8Array(analyser.frequencyBinCount);
  131. analyser.getByteFrequencyData(dataArray);
  132. const averageVolume = dataArray.reduce((a, b) => a + b) / dataArray.length;
  133. volumeIndicator.style.height = `${averageVolume / 255 * 100}px`; // Scale height based on volume
  134. requestAnimationFrame(updateVolumeIndicator);
  135. }
  136. function startReelAnimation() {
  137. reelImage.src = ANIMATED_REEL;
  138. }
  139. function stopReelAnimation() {
  140. reelImage.src = STATIC_REEL;
  141. }
  142. function addGapBetweenTracks(seconds) {
  143. return new Promise(resolve => {
  144. console.log(`Adding ${seconds} second gap`);
  145. const startTime = audioContext.currentTime;
  146. function checkGap() {
  147. const elapsed = audioContext.currentTime - startTime;
  148. if (elapsed >= seconds) {
  149. resolve();
  150. } else {
  151. requestAnimationFrame(checkGap);
  152. }
  153. }
  154. checkGap();
  155. });
  156. }
  157. let draggedItem = null;
  158. function handleDragStart(e) {
  159. draggedItem = e.target;
  160. e.target.style.opacity = '0.4';
  161. }
  162. function handleDragOver(e) {
  163. e.preventDefault();
  164. const targetItem = e.target;
  165. // Only handle drag over list items
  166. if (targetItem.tagName === 'LI') {
  167. const bounding = targetItem.getBoundingClientRect();
  168. const offset = bounding.y + (bounding.height/2);
  169. if (e.clientY - offset > 0) {
  170. targetItem.style.borderBottom = 'solid 2px #eed49f';
  171. targetItem.style.borderTop = '';
  172. } else {
  173. targetItem.style.borderTop = 'solid 2px #eed49f';
  174. targetItem.style.borderBottom = '';
  175. }
  176. }
  177. }
  178. function handleDrop(e) {
  179. e.preventDefault();
  180. const targetItem = e.target;
  181. // Only handle drops on list items
  182. if (targetItem.tagName === 'LI' && draggedItem !== targetItem) {
  183. // Get the current order of all items
  184. const items = Array.from(trackList.children);
  185. const oldIndex = items.indexOf(draggedItem);
  186. // Remove and insert the dragged item
  187. draggedItem.parentNode.removeChild(draggedItem);
  188. // Determine if dropping before or after the target
  189. const bounding = targetItem.getBoundingClientRect();
  190. const insertAfter = e.clientY > (bounding.top + bounding.height / 2);
  191. if (insertAfter) {
  192. targetItem.parentNode.insertBefore(draggedItem, targetItem.nextSibling);
  193. } else {
  194. targetItem.parentNode.insertBefore(draggedItem, targetItem);
  195. }
  196. // Get the new order of items
  197. const newItems = Array.from(trackList.children);
  198. const newIndex = newItems.indexOf(draggedItem);
  199. // Reorder the files array to match
  200. const [movedFile] = files.splice(oldIndex, 1);
  201. files.splice(newIndex, 0, movedFile);
  202. // Update all indices
  203. newItems.forEach((item, index) => {
  204. item.dataset.index = index;
  205. });
  206. // Log the new order for debugging
  207. logPlaylist();
  208. }
  209. clearDragOverStyles();
  210. }
  211. function handleDragEnd(e) {
  212. e.target.style.opacity = '';
  213. clearDragOverStyles();
  214. }
  215. function clearDragOverStyles() {
  216. const items = trackList.querySelectorAll('li');
  217. items.forEach(item => {
  218. item.style.borderTop = '';
  219. item.style.borderBottom = '';
  220. });
  221. }
  222. function updatePlaylistIndices() {
  223. const items = trackList.querySelectorAll('li');
  224. items.forEach((item, index) => {
  225. item.dataset.index = index;
  226. });
  227. }
  228. // Add this function to help with debugging
  229. function logPlaylist() {
  230. console.log("Current playlist order:");
  231. files.forEach((file, index) => {
  232. console.log(`${index}: ${file.name}`);
  233. });
  234. }