remove blank linez ;-)

This commit is contained in:
Armin 2026-07-07 14:20:13 +02:00
commit 4cd4649c0a

View file

@ -5,16 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>noiz://</title> <title>noiz://</title>
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #161821; color: #c6c8d1; height: 100vh; overflow: hidden; display: flex; flex-direction: column; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #161821; color: #c6c8d1; height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
.btn { background: linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; white-space: nowrap; box-shadow: 0 3px 0 #161821, 0 4px 6px rgba(0,0,0,0.4); } .btn { background: linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; white-space: nowrap; box-shadow: 0 3px 0 #161821, 0 4px 6px rgba(0,0,0,0.4); }
@ -71,7 +61,6 @@ canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
.playlist-toolbar .spacer { flex: 1; } .playlist-toolbar .spacer { flex: 1; }
.playlist-toolbar select { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 4px 20px 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; outline: none; box-shadow: 0 2px 0 #161821, 0 3px 4px rgba(0,0,0,0.3); transition: transform .1s, box-shadow .1s; -webkit-appearance: none; appearance: none; } .playlist-toolbar select { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 4px 20px 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; outline: none; box-shadow: 0 2px 0 #161821, 0 3px 4px rgba(0,0,0,0.3); transition: transform .1s, box-shadow .1s; -webkit-appearance: none; appearance: none; }
.playlist-toolbar select:hover { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #2e3348, #272c42); } .playlist-toolbar select:hover { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #2e3348, #272c42); }
.playlist-header { display: grid; grid-template-columns: 36px minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) 60px 60px; gap: 8px; padding: 8px 24px; font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #6b7089; border-bottom: 1px solid #272c42; flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); } .playlist-header { display: grid; grid-template-columns: 36px minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) 60px 60px; gap: 8px; padding: 8px 24px; font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #6b7089; border-bottom: 1px solid #272c42; flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); }
.playlist-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 2px 0; position: relative; background: #101219; } .playlist-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 2px 0; position: relative; background: #101219; }
#scanning { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(22,24,33,0.85); z-index: 10; font-size: 14px; color: #c6c8d1; flex-direction: column; gap: 10px; } #scanning { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(22,24,33,0.85); z-index: 10; font-size: 14px; color: #c6c8d1; flex-direction: column; gap: 10px; }
@ -122,14 +111,11 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
</style> </style>
</head> </head>
<body> <body>
<div id="noiz-overlay"> <div id="noiz-overlay">
<img src="noizmainlogo.png" alt="noiz://"> <img src="noizmainlogo.png" alt="noiz://">
</div> </div>
<div id="error"></div> <div id="error"></div>
<input type="file" id="folderPicker" webkitdirectory multiple style="display:none"> <input type="file" id="folderPicker" webkitdirectory multiple style="display:none">
<div class="content"> <div class="content">
<div class="pinned" id="pinned"> <div class="pinned" id="pinned">
<div class="player" id="player"> <div class="player" id="player">
@ -250,13 +236,11 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
<button class="btn" id="deleteBtn">Delete</button> <button class="btn" id="deleteBtn">Delete</button>
</div> </div>
</div> </div>
<footer id="footer"> <footer id="footer">
<span class="footer-track" id="footerTrack">No track</span> <span class="footer-track" id="footerTrack">No track</span>
<span id="footerTime">0:00 / 0:00</span> <span id="footerTime">0:00 / 0:00</span>
<span class="brand">noiz<span>://</span></span> <span class="brand">noiz<span>://</span></span>
</footer> </footer>
<script> <script>
const audioElA = new Audio(); const audioElA = new Audio();
const audioElB = new Audio(); const audioElB = new Audio();
@ -274,7 +258,6 @@ let crossfadeTimer = null;
let isCrossfading = false; let isCrossfading = false;
let repeatMode = 'off'; let repeatMode = 'off';
let shuffleOn = false; let shuffleOn = false;
let audioCtx = null; let audioCtx = null;
let analyser = null; let analyser = null;
let analyserL = null; let analyserL = null;
@ -285,8 +268,6 @@ let sortAsc = true;
let windowType = 'hanning'; let windowType = 'hanning';
let peakDecay = 0.985; let peakDecay = 0.985;
let barDecay = 0.96; let barDecay = 0.96;
function initAudioCtx() { function initAudioCtx() {
if (audioCtx) return; if (audioCtx) return;
audioCtx = new (window.AudioContext || window.webkitAudioContext)(); audioCtx = new (window.AudioContext || window.webkitAudioContext)();
@ -318,16 +299,13 @@ function initAudioCtx() {
analyser.connect(audioCtx.destination); analyser.connect(audioCtx.destination);
} catch (e) {} } catch (e) {}
} }
const canvas = document.getElementById('spectrum'); const canvas = document.getElementById('spectrum');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const bigTime = document.getElementById('bigTime'); const bigTime = document.getElementById('bigTime');
let peakData = []; let peakData = [];
let smoothedData = []; let smoothedData = [];
let vuPeakL = 0, vuPeakR = 0; let vuPeakL = 0, vuPeakR = 0;
let vuSmoothL = 0, vuSmoothR = 0; let vuSmoothL = 0, vuSmoothR = 0;
function bessi0(x) { function bessi0(x) {
let s = 1, t = 1; let s = 1, t = 1;
for (let k = 1; k <= 20; k++) { for (let k = 1; k <= 20; k++) {
@ -337,7 +315,6 @@ function bessi0(x) {
} }
return s; return s;
} }
function applyWindow(data, type) { function applyWindow(data, type) {
const n = data.length; const n = data.length;
let kaiserI0; let kaiserI0;
@ -380,7 +357,6 @@ function applyWindow(data, type) {
data[i] *= w; data[i] *= w;
} }
} }
function fft(re, im) { function fft(re, im) {
const n = re.length; const n = re.length;
for (let i = 1, j = 0; i < n; i++) { for (let i = 1, j = 0; i < n; i++) {
@ -413,9 +389,7 @@ function fft(re, im) {
} }
} }
} }
const SCALE_EXP = { none: 1, hanning: 0.5, hamming: 0.48, blackman: 0.4, bartlett: 0.55, gaussian: 0.43, kaiser: 0.33, flatTop: 0.3 }; const SCALE_EXP = { none: 1, hanning: 0.5, hamming: 0.48, blackman: 0.4, bartlett: 0.55, gaussian: 0.43, kaiser: 0.33, flatTop: 0.3 };
function drawSpectrum() { function drawSpectrum() {
if (!analyser) { animId = requestAnimationFrame(drawSpectrum); return; } if (!analyser) { animId = requestAnimationFrame(drawSpectrum); return; }
const fftSize = analyser.fftSize; const fftSize = analyser.fftSize;
@ -559,16 +533,13 @@ function drawSpectrum() {
} }
animId = requestAnimationFrame(drawSpectrum); animId = requestAnimationFrame(drawSpectrum);
} }
drawSpectrum(); drawSpectrum();
let tracks = []; let tracks = [];
let currentIndex = -1; let currentIndex = -1;
let selectedIndices = new Set(); let selectedIndices = new Set();
let anchorTrack = null; let anchorTrack = null;
let dragIndices = null; let dragIndices = null;
let dirHandle = null; let dirHandle = null;
const addFolderBtn = document.getElementById('addFolderBtn'); const addFolderBtn = document.getElementById('addFolderBtn');
const addFilesBtn = document.getElementById('addFilesBtn'); const addFilesBtn = document.getElementById('addFilesBtn');
const folderPicker = document.getElementById('folderPicker'); const folderPicker = document.getElementById('folderPicker');
@ -603,9 +574,7 @@ const windowSelect = document.getElementById('windowSelect');
const decaySelect = document.getElementById('decaySelect'); const decaySelect = document.getElementById('decaySelect');
const barDecaySelect = document.getElementById('barDecaySelect'); const barDecaySelect = document.getElementById('barDecaySelect');
const crossfadeSelect = document.getElementById('crossfadeSelect'); const crossfadeSelect = document.getElementById('crossfadeSelect');
const AUDIO_EXTS = new Set(['.mp3','.wav','.ogg','.flac','.m4a','.aac','.wma','.aiff','.alac','.opus']); const AUDIO_EXTS = new Set(['.mp3','.wav','.ogg','.flac','.m4a','.aac','.wma','.aiff','.alac','.opus']);
const GENRES = [ const GENRES = [
'Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal', 'Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal',
'New Age','Oldies','Other','Pop','R&B','Rap','Reggae','Rock','Techno','Industrial', 'New Age','Oldies','Other','Pop','R&B','Rap','Reggae','Rock','Techno','Industrial',
@ -627,20 +596,17 @@ const GENRES = [
'Christian Gangsta Rap','Heavy Metal','Black Metal','Crossover','Contemporary Christian', 'Christian Gangsta Rap','Heavy Metal','Black Metal','Crossover','Contemporary Christian',
'Christian Rock','Merengue','Salsa','Thrash Metal','Anime','JPop','Synthpop' 'Christian Rock','Merengue','Salsa','Thrash Metal','Anime','JPop','Synthpop'
]; ];
function fmtTime(s) { function fmtTime(s) {
if (!isFinite(s) || s < 0) return '0:00'; if (!isFinite(s) || s < 0) return '0:00';
const m = Math.floor(s / 60); const m = Math.floor(s / 60);
const sec = Math.floor(s % 60); const sec = Math.floor(s % 60);
return `${m}:${sec.toString().padStart(2, '0')}`; return `${m}:${sec.toString().padStart(2, '0')}`;
} }
function showError(msg) { function showError(msg) {
errorEl.textContent = msg; errorEl.textContent = msg;
errorEl.style.display = 'block'; errorEl.style.display = 'block';
setTimeout(() => { errorEl.style.display = 'none'; }, 5000); setTimeout(() => { errorEl.style.display = 'none'; }, 5000);
} }
async function* walkDir(dirHandle, path = '') { async function* walkDir(dirHandle, path = '') {
for await (const entry of dirHandle.values()) { for await (const entry of dirHandle.values()) {
const fullPath = path ? `${path}/${entry.name}` : entry.name; const fullPath = path ? `${path}/${entry.name}` : entry.name;
@ -651,13 +617,11 @@ async function* walkDir(dirHandle, path = '') {
} }
} }
} }
function isAudioFile(name) { function isAudioFile(name) {
const dot = name.lastIndexOf('.'); const dot = name.lastIndexOf('.');
if (dot === -1) return false; if (dot === -1) return false;
return AUDIO_EXTS.has(name.slice(dot).toLowerCase()); return AUDIO_EXTS.has(name.slice(dot).toLowerCase());
} }
function readBytes(file, start, length) { function readBytes(file, start, length) {
const blob = file.slice(start, start + length); const blob = file.slice(start, start + length);
return new Promise((resolve) => { return new Promise((resolve) => {
@ -666,7 +630,6 @@ function readBytes(file, start, length) {
r.readAsArrayBuffer(blob); r.readAsArrayBuffer(blob);
}); });
} }
function decodeText(arr, offset, len, enc) { function decodeText(arr, offset, len, enc) {
const end = Math.min(offset + len, arr.length); const end = Math.min(offset + len, arr.length);
if (end <= offset) return ''; if (end <= offset) return '';
@ -687,7 +650,6 @@ function decodeText(arr, offset, len, enc) {
return new TextDecoder('utf-8').decode(slice).replace(/\0/g, '').trim(); return new TextDecoder('utf-8').decode(slice).replace(/\0/g, '').trim();
} catch { return ''; } } catch { return ''; }
} }
function parseSyncsafeInt(bytes, off, len) { function parseSyncsafeInt(bytes, off, len) {
let val = 0; let val = 0;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
@ -695,7 +657,6 @@ function parseSyncsafeInt(bytes, off, len) {
} }
return val; return val;
} }
function parseGenre(text) { function parseGenre(text) {
if (!text) return ''; if (!text) return '';
const m = text.match(/\((\d+)\)/); const m = text.match(/\((\d+)\)/);
@ -703,7 +664,6 @@ function parseGenre(text) {
if (!isNaN(text) && text !== '') return GENRES[parseInt(text)] || text; if (!isNaN(text) && text !== '') return GENRES[parseInt(text)] || text;
return text; return text;
} }
async function readId3v2(file) { async function readId3v2(file) {
try { try {
const hdr = await readBytes(file, 0, 10); const hdr = await readBytes(file, 0, 10);
@ -744,7 +704,6 @@ async function readId3v2(file) {
return tags; return tags;
} catch { return null; } } catch { return null; }
} }
async function readId3v1(file) { async function readId3v1(file) {
try { try {
if (file.size < 128) return null; if (file.size < 128) return null;
@ -765,7 +724,6 @@ async function readId3v1(file) {
return tags; return tags;
} catch { return null; } } catch { return null; }
} }
async function readTags(file) { async function readTags(file) {
let tags = await readId3v2(file); let tags = await readId3v2(file);
if (tags && (tags.title || tags.artist)) return tags; if (tags && (tags.title || tags.artist)) return tags;
@ -781,7 +739,6 @@ async function readTags(file) {
} }
return tags || {}; return tags || {};
} }
const MP3_BITRATES = [ const MP3_BITRATES = [
[0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0], [0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0],
[0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0], [0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0],
@ -789,22 +746,17 @@ const MP3_BITRATES = [
[0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0], [0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0],
[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0] [0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0]
]; ];
const MP3_SAMPLERATES = [44100, 48000, 32000, 0, 22050, 24000, 16000, 0, 11025, 12000, 8000, 0]; const MP3_SAMPLERATES = [44100, 48000, 32000, 0, 22050, 24000, 16000, 0, 11025, 12000, 8000, 0];
const MP3_CHANNELS = ['Stereo', 'Joint Stereo', 'Dual Channel', 'Mono']; const MP3_CHANNELS = ['Stereo', 'Joint Stereo', 'Dual Channel', 'Mono'];
function isMp3Sync(b) { function isMp3Sync(b) {
return b[0] === 0xFF && (b[1] & 0xE0) === 0xE0 && ((b[1] >> 3) & 3) !== 1 && ((b[1] >> 1) & 3) !== 0; return b[0] === 0xFF && (b[1] & 0xE0) === 0xE0 && ((b[1] >> 3) & 3) !== 1 && ((b[1] >> 1) & 3) !== 0;
} }
function findMp3Header(data, off) { function findMp3Header(data, off) {
for (let i = off; i < data.length - 4; i++) { for (let i = off; i < data.length - 4; i++) {
if (isMp3Sync(data.slice(i, i + 4))) return i; if (isMp3Sync(data.slice(i, i + 4))) return i;
} }
return -1; return -1;
} }
async function readAudioInfo(file) { async function readAudioInfo(file) {
const info = {}; const info = {};
const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.')); const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.'));
@ -860,7 +812,6 @@ async function readAudioInfo(file) {
} catch {} } catch {}
return info; return info;
} }
async function processFiles(files, paths) { async function processFiles(files, paths) {
scanning.style.display = 'flex'; scanning.style.display = 'flex';
const total = files.length; const total = files.length;
@ -895,7 +846,6 @@ async function processFiles(files, paths) {
setTimeout(() => { scanning.style.display = 'none'; }, 800); setTimeout(() => { scanning.style.display = 'none'; }, 800);
return found; return found;
} }
async function addFolder() { async function addFolder() {
if ('showDirectoryPicker' in window) { if ('showDirectoryPicker' in window) {
try { try {
@ -924,7 +874,6 @@ async function addFolder() {
} }
folderPicker.click(); folderPicker.click();
} }
folderPicker.addEventListener('change', async (e) => { folderPicker.addEventListener('change', async (e) => {
const fileList = e.target.files; const fileList = e.target.files;
if (!fileList || fileList.length === 0) return; if (!fileList || fileList.length === 0) return;
@ -934,7 +883,6 @@ folderPicker.addEventListener('change', async (e) => {
addTracks(processed); addTracks(processed);
folderPicker.value = ''; folderPicker.value = '';
}); });
async function addFiles() { async function addFiles() {
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'file'; input.type = 'file';
@ -950,7 +898,6 @@ async function addFiles() {
}); });
input.click(); input.click();
} }
function addTracks(newTracks) { function addTracks(newTracks) {
if (newTracks.length === 0) { if (newTracks.length === 0) {
showError('No audio files found.'); showError('No audio files found.');
@ -959,7 +906,6 @@ function addTracks(newTracks) {
tracks = tracks.concat(newTracks); tracks = tracks.concat(newTracks);
renderTracks(); renderTracks();
} }
function isolateTracks() { function isolateTracks() {
tracks.forEach((t, i) => { tracks.forEach((t, i) => {
if (i !== currentIndex) URL.revokeObjectURL(t.url); if (i !== currentIndex) URL.revokeObjectURL(t.url);
@ -975,7 +921,6 @@ function isolateTracks() {
} }
renderTracks(); renderTracks();
} }
function deleteTrack() { function deleteTrack() {
if (selectedIndices.size === 0) return; if (selectedIndices.size === 0) return;
if (selectedIndices.size === tracks.length) { if (selectedIndices.size === tracks.length) {
@ -1006,7 +951,6 @@ function deleteTrack() {
renderTracks(); renderTracks();
} }
} }
function getSortFn(field) { function getSortFn(field) {
switch (field) { switch (field) {
case 'idx': return (a, b) => tracks.indexOf(a) - tracks.indexOf(b); case 'idx': return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
@ -1018,7 +962,6 @@ function getSortFn(field) {
default: return (a, b) => tracks.indexOf(a) - tracks.indexOf(b); default: return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
} }
} }
function renderTracks() { function renderTracks() {
const sorted = [...tracks].sort(getSortFn(sortField)); const sorted = [...tracks].sort(getSortFn(sortField));
if (!sortAsc) sorted.reverse(); if (!sortAsc) sorted.reverse();
@ -1120,13 +1063,11 @@ function renderTracks() {
canvas.style.display = hasT ? 'block' : 'none'; canvas.style.display = hasT ? 'block' : 'none';
progressRow.style.display = hasT ? 'flex' : 'none'; progressRow.style.display = hasT ? 'flex' : 'none';
} }
function escHtml(s) { function escHtml(s) {
const d = document.createElement('div'); const d = document.createElement('div');
d.textContent = s; d.textContent = s;
return d.innerHTML; return d.innerHTML;
} }
function playTrack(i) { function playTrack(i) {
if (i < 0 || i >= tracks.length) return; if (i < 0 || i >= tracks.length) return;
const prevIdx = currentIndex; const prevIdx = currentIndex;
@ -1173,7 +1114,6 @@ function playTrack(i) {
renderTracks(); renderTracks();
} }
} }
function updateUI() { function updateUI() {
const t = tracks[currentIndex]; const t = tracks[currentIndex];
if (!t) return; if (!t) return;
@ -1191,7 +1131,6 @@ function updateUI() {
else if (ai.channels) parts.push(ai.channels === 1 ? 'Mono' : 'Stereo'); else if (ai.channels) parts.push(ai.channels === 1 ? 'Mono' : 'Stereo');
audioInfo.textContent = parts.join(' \u00B7 '); audioInfo.textContent = parts.join(' \u00B7 ');
} }
function onLoadedmetadata(e) { function onLoadedmetadata(e) {
if (e.target !== audio) return; if (e.target !== audio) return;
const t = tracks[currentIndex]; const t = tracks[currentIndex];
@ -1245,7 +1184,6 @@ audioElA.addEventListener('play', onPlay);
audioElB.addEventListener('play', onPlay); audioElB.addEventListener('play', onPlay);
audioElA.addEventListener('pause', onPause); audioElA.addEventListener('pause', onPause);
audioElB.addEventListener('pause', onPause); audioElB.addEventListener('pause', onPause);
playBtn.addEventListener('click', () => { playBtn.addEventListener('click', () => {
if (tracks.length === 0) return; if (tracks.length === 0) return;
const selArr = [...selectedIndices]; const selArr = [...selectedIndices];
@ -1262,10 +1200,8 @@ playBtn.addEventListener('click', () => {
audio.pause(); audio.pause();
} }
}); });
prevBtn.addEventListener('click', prevTrack); prevBtn.addEventListener('click', prevTrack);
nextBtn.addEventListener('click', nextTrack); nextBtn.addEventListener('click', nextTrack);
function prevTrack() { function prevTrack() {
if (tracks.length === 0) return; if (tracks.length === 0) return;
if (audio.currentTime > 3) { audio.currentTime = 0; return; } if (audio.currentTime > 3) { audio.currentTime = 0; return; }
@ -1277,7 +1213,6 @@ function prevTrack() {
playTrack(currentIndex <= 0 ? tracks.length - 1 : currentIndex - 1); playTrack(currentIndex <= 0 ? tracks.length - 1 : currentIndex - 1);
} }
} }
function nextTrack() { function nextTrack() {
if (tracks.length === 0) return; if (tracks.length === 0) return;
if (shuffleOn) { if (shuffleOn) {
@ -1290,31 +1225,26 @@ function nextTrack() {
playTrack(currentIndex >= tracks.length - 1 ? 0 : currentIndex + 1); playTrack(currentIndex >= tracks.length - 1 ? 0 : currentIndex + 1);
} }
} }
progressBar.addEventListener('click', (e) => { progressBar.addEventListener('click', (e) => {
if (!audio.duration) return; if (!audio.duration) return;
const rect = progressBar.getBoundingClientRect(); const rect = progressBar.getBoundingClientRect();
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration; audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
}); });
volumeSlider.addEventListener('input', () => { volumeSlider.addEventListener('input', () => {
const v = parseFloat(volumeSlider.value); const v = parseFloat(volumeSlider.value);
audioElA.volume = v; audioElA.volume = v;
audioElB.volume = v; audioElB.volume = v;
volDisplay.textContent = Math.round(v * 100); volDisplay.textContent = Math.round(v * 100);
}); });
addFolderBtn.addEventListener('click', addFolder); addFolderBtn.addEventListener('click', addFolder);
addFilesBtn.addEventListener('click', addFiles); addFilesBtn.addEventListener('click', addFiles);
isolateBtn.addEventListener('click', isolateTracks); isolateBtn.addEventListener('click', isolateTracks);
deleteBtn.addEventListener('click', deleteTrack); deleteBtn.addEventListener('click', deleteTrack);
sortSelect.addEventListener('change', () => { sortSelect.addEventListener('change', () => {
if (sortSelect.value === sortField) { sortAsc = !sortAsc; } if (sortSelect.value === sortField) { sortAsc = !sortAsc; }
else { sortField = sortSelect.value; sortAsc = true; } else { sortField = sortSelect.value; sortAsc = true; }
renderTracks(); renderTracks();
}); });
windowSelect.addEventListener('change', () => { windowType = windowSelect.value; }); windowSelect.addEventListener('change', () => { windowType = windowSelect.value; });
decaySelect.addEventListener('change', () => { peakDecay = parseFloat(decaySelect.value); }); decaySelect.addEventListener('change', () => { peakDecay = parseFloat(decaySelect.value); });
barDecaySelect.addEventListener('change', () => { barDecay = parseFloat(barDecaySelect.value); }); barDecaySelect.addEventListener('change', () => { barDecay = parseFloat(barDecaySelect.value); });
@ -1323,11 +1253,9 @@ crossfadeDuration = parseFloat(crossfadeSelect.value);
repeatSelect.addEventListener('change', () => { repeatMode = repeatSelect.value; }); repeatSelect.addEventListener('change', () => { repeatMode = repeatSelect.value; });
shuffleSelect.addEventListener('change', () => { shuffleOn = shuffleSelect.value === 'on'; }); shuffleSelect.addEventListener('change', () => { shuffleOn = shuffleSelect.value === 'on'; });
volDisplay.textContent = Math.round(parseFloat(volumeSlider.value) * 100); volDisplay.textContent = Math.round(parseFloat(volumeSlider.value) * 100);
let dragCounter = 0; let dragCounter = 0;
let isInternalDrag = false; let isInternalDrag = false;
const content = document.querySelector('.content'); const content = document.querySelector('.content');
content.addEventListener('dragenter', (e) => { content.addEventListener('dragenter', (e) => {
if (isInternalDrag) return; if (isInternalDrag) return;
e.preventDefault(); e.preventDefault();
@ -1335,19 +1263,16 @@ content.addEventListener('dragenter', (e) => {
content.style.outline = '2px dashed #84a0c6'; content.style.outline = '2px dashed #84a0c6';
content.style.outlineOffset = '-12px'; content.style.outlineOffset = '-12px';
}); });
content.addEventListener('dragleave', (e) => { content.addEventListener('dragleave', (e) => {
if (isInternalDrag) return; if (isInternalDrag) return;
e.preventDefault(); e.preventDefault();
dragCounter--; dragCounter--;
if (dragCounter <= 0) { dragCounter = 0; content.style.outline = 'none'; } if (dragCounter <= 0) { dragCounter = 0; content.style.outline = 'none'; }
}); });
content.addEventListener('dragover', (e) => { content.addEventListener('dragover', (e) => {
if (isInternalDrag) { e.preventDefault(); return; } if (isInternalDrag) { e.preventDefault(); return; }
e.preventDefault(); e.preventDefault();
}); });
content.addEventListener('drop', async (e) => { content.addEventListener('drop', async (e) => {
if (isInternalDrag) return; if (isInternalDrag) return;
e.preventDefault(); e.preventDefault();
@ -1386,7 +1311,6 @@ content.addEventListener('drop', async (e) => {
const processed = await processFiles(files, paths); const processed = await processFiles(files, paths);
addTracks(processed); addTracks(processed);
}); });
async function collectFiles(entry, files, paths, path, onFile) { async function collectFiles(entry, files, paths, path, onFile) {
if (entry.isFile) { if (entry.isFile) {
const file = await new Promise((resolve) => entry.file(resolve)); const file = await new Promise((resolve) => entry.file(resolve));
@ -1409,14 +1333,12 @@ async function collectFiles(entry, files, paths, path, onFile) {
await readAll(); await readAll();
} }
} }
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return; if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
if (e.code === 'Space') { e.preventDefault(); playBtn.click(); } if (e.code === 'Space') { e.preventDefault(); playBtn.click(); }
if (e.code === 'ArrowRight') nextBtn.click(); if (e.code === 'ArrowRight') nextBtn.click();
if (e.code === 'ArrowLeft') prevBtn.click(); if (e.code === 'ArrowLeft') prevBtn.click();
}); });
window.addEventListener('focus', () => { window.addEventListener('focus', () => {
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume(); if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
}); });