sonodrill

Sonodrill, app iOS/Android d’entraînement musical (repères, boucles, vitesse), Kotlin Multiplatform + Compose
git clone https://git.schurger.org/sonodrill.git
Log | Files | Refs

commit 9266199620d9d85efa5e252deffd3e83c7cbe831
parent ef05a889587ef3989b1f43ad82707ea46db49165
Author: Jean Schurger <jean@schurger.org>
Date:   Fri, 16 Oct 2026 15:11:09 -0400

Skip and loop over the same span no longer conflict

Adding a skip zone over a loop zone's span removes that loop (stopping it if
active); looping between marks removes a skip zone over the same span.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141Zm4UQq8PATQAG98MdNCc

Diffstat:
McomposeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_view_model.kt | 18+++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_view_model.kt b/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_view_model.kt @@ -186,7 +186,7 @@ class PlayerViewModel( if (next_start != null) { val next_end = sorted.firstOrNull { it.position_ms > next_start.position_ms } if (next_end != null) { - loop.set_loop(next_start, next_end) + loop_between(next_start, next_end) return } } @@ -217,7 +217,7 @@ class PlayerViewModel( val sorted = _marks.value.sortedBy { it.position_ms } val prev = sorted.lastOrNull { it.position_ms <= position_ms } val next = sorted.firstOrNull { it.position_ms > position_ms } - if (prev != null && next != null) loop.set_loop(prev, next) + if (prev != null && next != null) loop_between(prev, next) } fun add_mark_and_loop() { @@ -256,7 +256,15 @@ class PlayerViewModel( val sorted = _marks.value.sortedBy { it.position_ms } val a = sorted.lastOrNull { it.position_ms <= zone.start_ms } ?: MarkInfo(id = "_lz_a", file_id = "", position_ms = zone.start_ms) val b = sorted.firstOrNull { it.position_ms >= zone.end_ms } ?: MarkInfo(id = "_lz_b", file_id = "", position_ms = zone.end_ms) - loop.set_loop(a, b, seek_to_start = false) + loop_between(a, b, seek_to_start = false) + } + + // Loops between marks; a skip zone over the same span would jump out of the loop, so it goes + private fun loop_between(a: MarkInfo, b: MarkInfo, seek_to_start: Boolean = true) { + val start = minOf(a.position_ms, b.position_ms) + val end = maxOf(a.position_ms, b.position_ms) + set_skip_zones(_skip_zones.value.filter { !(it.start_ms == start && it.end_ms == end) }) + loop.set_loop(a, b, seek_to_start) } fun toggle_full_loop() { @@ -296,6 +304,10 @@ class PlayerViewModel( val prev = sorted.lastOrNull { it.position_ms <= pos } ?: return val next = sorted.firstOrNull { it.position_ms > pos } ?: return val zone = SkipZone(prev.position_ms, next.position_ms) + // Skipping a span replaces a loop over the same span (and stops it if it's playing) + val same_loop = LoopZone(zone.start_ms, zone.end_ms) + if (loop_state.value.is_zone(same_loop)) loop.clear() + set_loop_zones(_loop_zones.value.filter { it != same_loop }) if (_skip_zones.value.none { it == zone }) set_skip_zones((_skip_zones.value + zone).sortedBy { it.start_ms }) }