From 5b493e5b4e0b50bf07554cdfe01704d6f160bcc0 Mon Sep 17 00:00:00 2001 From: Hasinjato Date: Tue, 8 Sep 2026 10:32:07 +0300 Subject: [PATCH] Implement A-B looping based on measure - Android --- .../kotlin/mg/dot/feufaro/midi/MidiPlayer.kt | 42 +++++++++++++++---- .../mg/dot/feufaro/solfa/TimeUnitObject.kt | 4 +- .../feufaro/viewmodel/SharedScreenModel.kt | 8 ++-- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt index 1e69b44..6a51f48 100644 --- a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt @@ -391,10 +391,9 @@ actual class FMediaPlayer actual constructor( lastEventNano = System.nanoTime() // A-B loop - if (isLoopingAB && pointA >= 0 && pointB > pointA && - ticksToMs(currentTickPos) >= pointB) { + if (isLoopingAB && pointA >= 0L && pointB > pointA && currentTickPos >= pointB) { allNotesOff() - currentTickPos = msToTicks(pointA) + currentTickPos = pointA clockNano = System.nanoTime() clockTick = currentTickPos idx = events.indexOfFirst { it.tickAbsolute >= currentTickPos } @@ -992,13 +991,38 @@ actual class FMediaPlayer actual constructor( startNavigationMonitor(sharedScreenModel) startSyncLoop(sharedScreenModel) } - actual fun setPointA() { pointA = getCurrentPosition() } - actual fun setPointB() { - pointB = getCurrentPosition() - if (pointB > pointA && pointA != -1L) isLoopingAB = true + actual fun setPointA(grid: Int) { + val tick = grid.toLong() * resolution + pointA = tick + + val currentB = sharedScreenModel.loopingState.value.second + sharedScreenModel.updateLoopingState(grid, currentB) } - actual fun clearLoop() { isLoopingAB = false; pointA = -1L; pointB = -1L } - actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB) + + actual fun setPointB(grid: Int) { + val tick = (((grid + 1).toDouble() * resolution) - 1.0).toLong() + pointB = tick.coerceAtLeast(0L) + + val currentA = sharedScreenModel.loopingState.value.first + sharedScreenModel.updateLoopingState(currentA, grid) + + if (currentA != -1) { + seekToGrid(currentA) + } + + if (pointB > pointA && pointA != -1L) { + isLoopingAB = true + } + } + + actual fun clearLoop() { + isLoopingAB = false + pointA = -1 + pointB = -1 + } + + actual fun getLoopState() = Triple(pointA.toLong(), pointB.toLong(), isLoopingAB) + actual fun toggleVoice(index: Int) { applyVoiceStates() } private fun saveVoicesVolumes() { val data = voiceVolumes.joinToString(",") diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt index c39278d..deb9180 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -639,7 +639,6 @@ fun LazyVerticalGridTUO( val platform = getPlatform() val isAndroid = platform.name.startsWith("Android") - val loopingState by sharedScreenModel.loopingState.collectAsState() val activeIndex by sharedScreenModel.activeIndex.collectAsState() val loopState by produceState(Triple(-1L, -1L, false), sharedScreenModel.mediaPlayer) { while (true) { @@ -1011,7 +1010,8 @@ fun LazyVerticalGridTUO( Row(modifier = Modifier.fillMaxWidth()) { measureTUOs.forEachIndexed { indexInMeasure, oneTUO -> val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure - val pointA = loopingState.first + val loopingState by sharedScreenModel.loopingState.collectAsState() + val pointA = loopingState.first val pointB = loopingState.second val isInABrange = if (pointA != -1 && pointB != -1) { diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt index 39d52b9..be6ebe1 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt @@ -445,11 +445,13 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode } } - private val _loopingState = MutableStateFlow(Pair(-1, -1)) - val loopingState: StateFlow> = _loopingState.asStateFlow() + data class LoopRange(val first: Int = -1, val second: Int = -1) + + private val _loopingState = MutableStateFlow(LoopRange()) + val loopingState: StateFlow = _loopingState.asStateFlow() fun updateLoopingState(a: Int, b: Int) { - _loopingState.value = Pair(a, b) + _loopingState.value = LoopRange(a, b) } fun calculateSmartPointB(rawPointB: Int, isZeroIndexed: Boolean = false): Int {