@@ -675,6 +683,10 @@ button.cut:hover:not(:disabled){background:#1e3a8a}
RMS 0–1 · sections above this value are marked loud
+
+
+ seconds to rewind before section start
@@ -723,6 +735,23 @@ const fmtT = s => {
};
const pad = n => String(n).padStart(2,'0');
+function announce(msg) {
+ const el = document.getElementById('sr-announce');
+ if (!el) return;
+ el.textContent = ''; // clear first so same text re-triggers
+ setTimeout(() => { el.textContent = msg; }, 50);
+}
+const getPreroll = () => {
+ const v = parseFloat(document.getElementById('preroll-input').value);
+ return isNaN(v) || v < 0 ? 0 : v;
+};
+function setCutFields(idx, startSec, endSec) {
+ const startEl = document.getElementById('cut-start-'+idx);
+ const endEl = document.getElementById('cut-end-'+idx);
+ if (startEl) startEl.value = fmtT(startSec);
+ if (endEl && endSec != null) endEl.value = fmtT(endSec);
+}
+
// idx -> filename, for live-status polling
const recMap = new Map();
// idx -> [{start,end}], populated after analysis
@@ -804,19 +833,20 @@ function parseTime(s) {
return parts[0];
}
-function seekToSection(idx, filename, startSec, endSec) {
+function seekToSection(idx, filename, startSec, endSec, sectionIdx) {
const pbtn = document.getElementById('pbtn-'+idx);
if (pbtn.getAttribute('aria-expanded') !== 'true') togglePlayer(idx, filename);
activePlayerIdx = idx;
- const audio = document.getElementById('aud-'+idx);
- const doSeek = () => { audio.currentTime = startSec; };
+ const audio = document.getElementById('aud-'+idx);
+ const seekTo = Math.max(0, startSec - getPreroll());
+ const doSeek = () => { audio.currentTime = seekTo; };
if (audio.readyState >= 1) doSeek();
else audio.addEventListener('loadedmetadata', doSeek, {once: true});
- // Pre-fill cut panel fields with section boundaries
- const startEl = document.getElementById('cut-start-'+idx);
- const endEl = document.getElementById('cut-end-'+idx);
- if (startEl) startEl.value = fmtT(startSec);
- if (endEl && endSec != null) endEl.value = fmtT(endSec);
+ setCutFields(idx, startSec, endSec);
+ if (sectionIdx != null) {
+ const total = (sectionMap.get(idx) || []).length;
+ announce(`Section ${sectionIdx + 1} of ${total}: ${fmtT(startSec)} to ${fmtT(endSec)}`);
+ }
}
async function analyse(idx, filename, cell, btn) {
@@ -842,12 +872,12 @@ async function analyse(idx, filename, cell, btn) {
chips.setAttribute('aria-label','Loud sections — click to jump, J/K to step');
if (d.sections && d.sections.length) {
sectionMap.set(idx, d.sections);
- d.sections.forEach(s => {
+ d.sections.forEach((s, si) => {
const c = document.createElement('button');
c.className='chip'; c.setAttribute('role','listitem');
c.title = 'Jump to this section (or use J/K keys)';
c.textContent = `${fmtT(s.start)} – ${fmtT(s.end)}`;
- c.addEventListener('click', () => seekToSection(idx, filename, s.start, s.end));
+ c.addEventListener('click', () => seekToSection(idx, filename, s.start, s.end, si));
chips.appendChild(c);
});
} else {
@@ -873,20 +903,38 @@ document.addEventListener('keydown', e => {
if (!sections.length) return;
const audio = document.getElementById('aud-'+activePlayerIdx);
if (!audio) return;
+ const preroll = getPreroll();
+
if (e.key === 'j' || e.key === 'J') {
+ e.preventDefault();
const cur = audio.currentTime;
- let target = sections[0].start;
+ let targetIdx = -1;
for (let i = sections.length - 1; i >= 0; i--) {
- if (sections[i].start < cur - 1) { target = sections[i].start; break; }
+ if (sections[i].start < cur - 1) { targetIdx = i; break; }
+ }
+ if (targetIdx >= 0) {
+ const s = sections[targetIdx];
+ audio.currentTime = Math.max(0, s.start - preroll);
+ setCutFields(activePlayerIdx, s.start, s.end);
+ announce(`Section ${targetIdx + 1} of ${sections.length}: ${fmtT(s.start)} to ${fmtT(s.end)}`);
+ } else {
+ announce('Beginning of sections');
}
- audio.currentTime = target;
- e.preventDefault();
} else if (e.key === 'k' || e.key === 'K') {
- const cur = audio.currentTime;
- for (const s of sections) {
- if (s.start > cur + 0.5) { audio.currentTime = s.start; break; }
- }
e.preventDefault();
+ const cur = audio.currentTime;
+ let jumped = false;
+ for (let i = 0; i < sections.length; i++) {
+ if (sections[i].start > cur + 0.5) {
+ const s = sections[i];
+ audio.currentTime = Math.max(0, s.start - preroll);
+ setCutFields(activePlayerIdx, s.start, s.end);
+ announce(`Section ${i + 1} of ${sections.length}: ${fmtT(s.start)} to ${fmtT(s.end)}`);
+ jumped = true;
+ break;
+ }
+ }
+ if (!jumped) announce('End of sections');
}
});