Skip to content

Commit 46febb1

Browse files
committed
fix(focus): keep From/To independent and normalize on play
1 parent 9cbd374 commit 46febb1

File tree

2 files changed

+64
-18
lines changed

2 files changed

+64
-18
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Fixed
10+
- Focus playback: segment bar-range resolution no longer shifts when muted voices are configured; From/To now resolve deterministically from score bar numbering.
11+
- Focus toolbar: `From` and `To` fields are now independent while editing (no cross-overwrite during typing).
12+
- Focus playback: when `To < From`, bounds are normalized by swapping only at Play/Resume time.
13+
14+
### Changed
15+
- Added/expanded automated Focus regression harness scenarios for reprise/volta boundary resolution and muted-voice invariance.
916

1017

1118
## [0.32.1] - 2026-02-13

src/renderer/renderer.js

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24604,6 +24604,7 @@ async function togglePlayPauseEffective() {
2460424604
}
2460524605

2460624606
if (isPaused) {
24607+
normalizeFocusLoopBoundsForPlayback();
2460724608
const plan = buildTransportPlaybackPlan();
2460824609
if (plan && plan.invalid) {
2460924610
showToast(plan.invalidReason || "Cannot start Focus playback.", 3200);
@@ -24626,6 +24627,7 @@ async function togglePlayPauseEffective() {
2462624627
if (played) return;
2462724628
}
2462824629

24630+
if (focusModeEnabled) normalizeFocusLoopBoundsForPlayback();
2462924631
const plan = focusModeEnabled
2463024632
? buildTransportPlaybackPlan()
2463124633
: (pendingPlaybackPlan || buildTransportPlaybackPlan());
@@ -24680,8 +24682,35 @@ async function transportTogglePlayPause() {
2468024682

2468124683
async function transportPlay() {
2468224684
if (isPlaying) return;
24685+
if (focusModeEnabled) normalizeFocusLoopBoundsForPlayback();
2468324686
if (isPaused) {
24684-
await startPlaybackFromRange();
24687+
const plan = buildTransportPlaybackPlan();
24688+
if (plan && plan.invalid) {
24689+
showToast(plan.invalidReason || "Cannot start Focus playback.", 3200);
24690+
return;
24691+
}
24692+
const resumeOffset = playbackRange ? Math.max(0, Number(playbackRange.startOffset) || 0) : 0;
24693+
await startPlaybackFromRange({
24694+
startOffset: resumeOffset,
24695+
endOffset: plan.rangeEnd,
24696+
origin: focusModeEnabled ? "focus" : "transport",
24697+
loop: plan.loopEnabled,
24698+
});
24699+
return;
24700+
}
24701+
if (focusModeEnabled) {
24702+
const plan = buildTransportPlaybackPlan();
24703+
if (plan && plan.invalid) {
24704+
showToast(plan.invalidReason || "Cannot start Focus playback.", 3200);
24705+
return;
24706+
}
24707+
applyPlaybackPlanSpeed(plan);
24708+
await startPlaybackFromRange({
24709+
startOffset: plan.rangeStart,
24710+
endOffset: plan.rangeEnd,
24711+
origin: "focus",
24712+
loop: plan.loopEnabled,
24713+
});
2468524714
return;
2468624715
}
2468724716
const startOffset = Math.max(0, Number(transportPlayheadOffset) || 0);
@@ -24694,6 +24723,7 @@ async function transportPause() {
2469424723
return;
2469524724
}
2469624725
if (isPaused) {
24726+
normalizeFocusLoopBoundsForPlayback();
2469724727
const plan = buildTransportPlaybackPlan();
2469824728
if (plan && plan.invalid) {
2469924729
showToast(plan.invalidReason || "Cannot start Focus playback.", 3200);
@@ -26311,16 +26341,33 @@ function updatePracticeUi() {
2631126341
}
2631226342
}
2631326343

26314-
function normalizeLoopBounds(fromMeasure, toMeasure, { changedField } = {}) {
26344+
function normalizeLoopBounds(fromMeasure, toMeasure) {
2631526345
const from = clampInt(fromMeasure, 0, 100000, 0);
2631626346
const to = clampInt(toMeasure, 0, 100000, 0);
26317-
if (from > 0 && to > 0 && from > to) {
26318-
if (changedField === "to") return { from: to, to };
26319-
return { from, to: from };
26320-
}
2632126347
return { from, to };
2632226348
}
2632326349

26350+
function normalizeFocusLoopBoundsForPlayback() {
26351+
if (!focusModeEnabled) return false;
26352+
const from = clampInt(playbackLoopFromMeasure, 0, 100000, 0);
26353+
const to = clampInt(playbackLoopToMeasure, 0, 100000, 0);
26354+
if (!(from > 0 && to > 0 && from > to)) return false;
26355+
playbackLoopFromMeasure = to;
26356+
playbackLoopToMeasure = from;
26357+
updatePracticeUi();
26358+
syncPendingPlaybackPlan();
26359+
const patch = {
26360+
playbackLoopFromMeasure: playbackLoopFromMeasure,
26361+
playbackLoopToMeasure: playbackLoopToMeasure,
26362+
};
26363+
if (activeTuneId) {
26364+
playbackLoopTuneId = String(activeTuneId);
26365+
patch.playbackLoopTuneId = playbackLoopTuneId;
26366+
}
26367+
persistLoopSettingsPatch(patch).catch(() => {});
26368+
return true;
26369+
}
26370+
2632426371
function maybeResetFocusLoopForTune(tuneId, { updateUi = true } = {}) {
2632526372
if (!focusModeEnabled) return;
2632626373
const id = tuneId != null ? String(tuneId) : "";
@@ -29499,17 +29546,13 @@ if ($practiceLoopEnabled) {
2949929546
if ($practiceLoopFrom) {
2950029547
$practiceLoopFrom.addEventListener("input", () => {
2950129548
const next = clampLoopField($practiceLoopFrom.value);
29502-
const normalized = normalizeLoopBounds(next, playbackLoopToMeasure, { changedField: "from" });
29503-
playbackLoopFromMeasure = normalized.from;
29504-
playbackLoopToMeasure = normalized.to;
29549+
playbackLoopFromMeasure = next;
2950529550
syncPendingPlaybackPlan();
2950629551
updatePracticeUi();
2950729552
});
2950829553
$practiceLoopFrom.addEventListener("change", () => {
2950929554
const next = clampLoopField($practiceLoopFrom.value);
29510-
const normalized = normalizeLoopBounds(next, playbackLoopToMeasure, { changedField: "from" });
29511-
playbackLoopFromMeasure = normalized.from;
29512-
playbackLoopToMeasure = normalized.to;
29555+
playbackLoopFromMeasure = next;
2951329556
syncPendingPlaybackPlan();
2951429557
updatePracticeUi();
2951529558
const patch = {
@@ -29527,17 +29570,13 @@ if ($practiceLoopFrom) {
2952729570
if ($practiceLoopTo) {
2952829571
$practiceLoopTo.addEventListener("input", () => {
2952929572
const next = clampLoopField($practiceLoopTo.value);
29530-
const normalized = normalizeLoopBounds(playbackLoopFromMeasure, next, { changedField: "to" });
29531-
playbackLoopFromMeasure = normalized.from;
29532-
playbackLoopToMeasure = normalized.to;
29573+
playbackLoopToMeasure = next;
2953329574
syncPendingPlaybackPlan();
2953429575
updatePracticeUi();
2953529576
});
2953629577
$practiceLoopTo.addEventListener("change", () => {
2953729578
const next = clampLoopField($practiceLoopTo.value);
29538-
const normalized = normalizeLoopBounds(playbackLoopFromMeasure, next, { changedField: "to" });
29539-
playbackLoopFromMeasure = normalized.from;
29540-
playbackLoopToMeasure = normalized.to;
29579+
playbackLoopToMeasure = next;
2954129580
syncPendingPlaybackPlan();
2954229581
updatePracticeUi();
2954329582
const patch = {

0 commit comments

Comments
 (0)