From 64e21108355f40598998061a50f51beb63097abe Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 4 Jul 2026 23:58:33 +0200 Subject: [PATCH] Audio Wave: snap splits to group line/frame + 'split @ groups' button Double-click now snaps the split to the nearest 721 group line when within ~9px (so a segment ends exactly on frame 721), else to the nearest frame (frame-exact splits). New 'split @ groups' button drops a split on every group boundary at once, so covering the 721 grid is one click. --- web/audio_wave.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/web/audio_wave.js b/web/audio_wave.js index bbf3c7a..a681847 100644 --- a/web/audio_wave.js +++ b/web/audio_wave.js @@ -64,10 +64,10 @@ function setupWave(node) { const bar = document.createElement("div"); bar.style.cssText = "display:flex;gap:6px;align-items:center;font-size:10px;color:#bbb;flex-wrap:wrap;"; const mk = (t) => { const b = document.createElement("button"); b.textContent = t; b.style.cssText = "font-size:10px;padding:1px 6px;"; return b; }; - const playBtn = mk("▶ play"), upBtn = mk("upload"), clearBtn = mk("clear splits"); + const playBtn = mk("▶ play"), upBtn = mk("upload"), grpBtn = mk("split @ groups"), clearBtn = mk("clear splits"); const readout = document.createElement("span"); readout.textContent = "0.00 / 0.00s"; - const hint = document.createElement("span"); hint.textContent = "click=seek · dblclick=add split · shift-click a split=remove"; - bar.append(playBtn, upBtn, clearBtn, readout, hint); + const hint = document.createElement("span"); hint.textContent = "click=seek · dblclick=add split (snaps to group line/frame) · shift-click a split=remove"; + bar.append(playBtn, upBtn, grpBtn, clearBtn, readout, hint); const canvas = document.createElement("canvas"); canvas.width = 900; canvas.height = 170; canvas.style.cssText = "width:100%;height:170px;background:#141418;border-radius:4px;cursor:pointer;"; @@ -181,10 +181,16 @@ function setupWave(node) { } st.audio.currentTime = Math.max(0, Math.min(st.duration, t)); draw(); // click = seek }); - canvas.addEventListener("dblclick", (e) => { // add a user split + function snap(t) { // snap to a group line if near, else to a frame + const gs = groupFrames() / fps(); + const gt = Math.round(t / gs) * gs; + if (gt > 0.02 && gt < st.duration - 0.02 && Math.abs(t2x(gt) - t2x(t)) < 9) return gt; + return Math.round(t * fps()) / fps(); + } + canvas.addEventListener("dblclick", (e) => { // add a user split (snapped) const r = canvas.getBoundingClientRect(); - const t = x2t(((e.clientX - r.left) / r.width) * canvas.width); - if (t > 0.05 && t < st.duration - 0.05) { st.splits.push(t); serialize(); syncNotesBox(); draw(); } + const t = snap(x2t(((e.clientX - r.left) / r.width) * canvas.width)); + if (t > 0.02 && t < st.duration - 0.02) { st.splits.push(t); serialize(); syncNotesBox(); draw(); } }); const loop = () => { if (!st.playing) return; draw(); requestAnimationFrame(loop); }; @@ -194,6 +200,12 @@ function setupWave(node) { }; st.audio.onended = () => { st.playing = false; playBtn.textContent = "▶ play"; draw(); }; clearBtn.onclick = () => { st.splits = []; serialize(); syncNotesBox(); draw(); }; // wipe stale user splits + grpBtn.onclick = () => { // one split on every 721 group line + const set = new Set(st.splits.map((s) => Math.round(s * 100) / 100)); + for (const g of groupBounds()) set.add(Math.round(g * 100) / 100); + st.splits = Array.from(set).sort((a, b) => a - b); + serialize(); syncNotesBox(); draw(); + }; upBtn.onclick = () => { const inp = document.createElement("input"); inp.type = "file"; inp.accept = "audio/*";