()
| 144 | } |
| 145 | |
| 146 | export function updateLoopRegionVisual() { |
| 147 | const regionItem = document.getElementById("t-export-region"); |
| 148 | const hasRegion = loopEnabled && totalDuration > 0 && loopEnd > loopStart; |
| 149 | if (regionItem) regionItem.setAttribute("aria-disabled", String(!hasRegion)); |
| 150 | // Keep the engine's loop bounds in sync with every loop change (toggle/drag); |
| 151 | // the engine wraps playback itself off these values. No-op on streaming path. |
| 152 | audioEngine?.setLoop(loopEnabled, loopStart, loopEnd); |
| 153 | if (!loopEnabled || !totalDuration) { |
| 154 | loopRegionEl.classList.add("hidden"); |
| 155 | return; |
| 156 | } |
| 157 | ensureLoopRegionParent(); |
| 158 | // Keep the loop overlay in the same normalized timeline coordinate |
| 159 | // system as the ruler ticks. Percentages avoid CSS zoom mismatch: |
| 160 | // pointer coordinates and getBoundingClientRect() are visual pixels, |
| 161 | // but style.left/style.width in px are unzoomed layout pixels. |
| 162 | const startPct = Math.max(0, Math.min(100, (loopStart / totalDuration) * 100)); |
| 163 | const endPct = Math.max(0, Math.min(100, (loopEnd / totalDuration) * 100)); |
| 164 | loopRegionEl.style.left = `${startPct}%`; |
| 165 | loopRegionEl.style.width = `${Math.max(0, endPct - startPct)}%`; |
| 166 | loopRegionEl.classList.remove("hidden"); |
| 167 | } |
| 168 | |
| 169 | // Standard DAW transport state machine: |
| 170 | // [stopped] (paused at start) ─Play→ [playing] |
no test coverage detected