Fix! A-B looping ux

This commit is contained in:
Hasinjato 2026-09-10 11:27:48 +03:00
parent bcd2d9a1f9
commit 540b508250
4 changed files with 86 additions and 37 deletions

View file

@ -1,6 +1,7 @@
package mg.dot.feufaro.solfa
import SharedScreenModel
import SharedScreenModel.ABModeState
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.*
@ -286,19 +287,21 @@ fun TimeUnitComposable(
parentFocusRequester: FocusRequester? = null,
transpositionInterval: Int,
hairpinRange: HairpinRange? = null,
isInABrange: Boolean = false
isInABrange: Boolean = false,
abModeState: ABModeState,
) {
val col = if (tuo.getNum() % 2 == 0) MaterialTheme.colorScheme.tertiary.copy(alpha = 0.05f) else MaterialTheme.colorScheme.error.copy(alpha = 0.05f)
val currentDensity = LocalDensity.current
val colorUnderline = MaterialTheme.colorScheme.onSecondary
val animatedColor = when {
gridActive -> animateColorAsState(
targetValue = MaterialTheme.colorScheme.secondary.copy(alpha = 1f),
animationSpec = tween(durationMillis = 100)
).value
val targetColor = when {
gridActive && abModeState != ABModeState.WAITING_FOR_A -> MaterialTheme.colorScheme.secondary
isInABrange -> MaterialTheme.colorScheme.tertiary.copy(alpha = 0.25f)
else -> col
}
val animatedColor by animateColorAsState(
targetValue = targetColor,
animationSpec = tween(durationMillis = 100)
)
val focusRequesters = remember { List(4) { FocusRequester() } }
@ -694,6 +697,7 @@ fun LazyVerticalGridTUO(
val tuoTimestamps by sharedScreenModel.tuoTimestamps.collectAsState()
val activeRowIndex by sharedScreenModel.activeIndex.collectAsStateWithLifecycle()
val abModeState by sharedScreenModel.abModeState.collectAsState()
val measures = remember(tuoList, gridColumnCount) {
tuoList.drop(1).chunked(gridColumnCount)
}
@ -1107,10 +1111,12 @@ fun LazyVerticalGridTUO(
selectedIndex = globalIndex
showEditDialog = true
} else {
val isAtStart = loopState.first == -1L
val isWaitingForB = loopState.first != -1L && !loopState.third
if (isWaitingForB) {
when (abModeState) {
ABModeState.WAITING_FOR_A -> {
sharedScreenModel.mediaPlayer?.setPointA(globalIndex)
sharedScreenModel.setABModeState(ABModeState.WAITING_FOR_B)
}
ABModeState.WAITING_FOR_B -> {
val enableSmartB = prefs.getBoolean("smartBpoint", true)
val smartIndex = if (enableSmartB) {
sharedScreenModel.calculateSmartPointB(globalIndex, isZeroIndexed = true)
@ -1118,11 +1124,15 @@ fun LazyVerticalGridTUO(
globalIndex
}
sharedScreenModel.mediaPlayer?.setPointB(smartIndex)
} else {
sharedScreenModel.setABModeState(ABModeState.LOOP_ACTIVE)
}
ABModeState.IDLE, ABModeState.LOOP_ACTIVE -> {
sharedScreenModel.seekToGrid(globalIndex)
sharedScreenModel.updateActiveIndexByIndex(globalIndex)
}
}
}
},
onDoubleTap = { offset ->
if(editMode) {
@ -1146,7 +1156,8 @@ fun LazyVerticalGridTUO(
parentFocusRequester = focusRequester,
transpositionInterval = currentInterval,
hairpinRange = hairpinByEndBlock[oneTUO.numBlock],
isInABrange = isInABrange
isInABrange = isInABrange,
abModeState = abModeState
)
if (showContextualMenu && selectedIndex == globalIndex) {

View file

@ -1,6 +1,7 @@
package mg.dot.feufaro.ui
import SharedScreenModel
import SharedScreenModel.ABModeState
import androidx.compose.animation.*
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
@ -361,19 +362,20 @@ fun MidiControlPanel(
verticalAlignment = Alignment.CenterVertically,
) {
Column {
val abModeState by sharedScreenModel.abModeState.collectAsState()
val isAtStart = loopState.first == -1L
val isWaitingForB = loopState.first != -1L && !loopState.third
val isLooping = loopState.third
val isWaitingForB = abModeState == ABModeState.WAITING_FOR_B
val isLooping = abModeState == ABModeState.LOOP_ACTIVE
val abButtonText = when (abModeState) {
ABModeState.WAITING_FOR_A -> "A ?"
ABModeState.WAITING_FOR_B -> "B ?"
ABModeState.IDLE, ABModeState.LOOP_ACTIVE -> "A B"
}
OutlinedButton(
onClick = {
when {
isAtStart -> mediaPlayer.setPointA(sharedScreenModel.activeIndex.value)
isWaitingForB -> mediaPlayer.setPointB(sharedScreenModel.activeIndex.value)
isLooping -> {
mediaPlayer.clearLoop()
}
}
sharedScreenModel.toggleABSelectionMode()
},
colors = ButtonDefaults.buttonColors(
containerColor = when {
@ -391,7 +393,7 @@ fun MidiControlPanel(
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "A",
text = abButtonText,
color = if (isWaitingForB || isLooping) Color.White else Color.Black,
fontSize = MaterialTheme.typography.titleLarge.fontSize
)
@ -402,12 +404,6 @@ fun MidiControlPanel(
tint = if (isLooping) Color.White else Color.Black,
modifier = Modifier.size(16.dp)
)
Text(
text = "B",
color = if (isLooping) Color.White else Color.Black,
fontSize = MaterialTheme.typography.titleLarge.fontSize
)
}
}
}

View file

@ -451,9 +451,37 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
val loopingState: StateFlow<LoopRange> = _loopingState.asStateFlow()
fun updateLoopingState(a: Int, b: Int) {
updateActiveIndexByIndex(a)
_loopingState.value = LoopRange(a, b)
}
enum class ABModeState {
IDLE,
WAITING_FOR_A,
WAITING_FOR_B,
LOOP_ACTIVE
}
private val _abModeState = MutableStateFlow(ABModeState.IDLE)
val abModeState: StateFlow<ABModeState> = _abModeState.asStateFlow()
fun toggleABSelectionMode() {
if(_isPlay.value) {
togglePlayPause()
}
_abModeState.value = when (_abModeState.value) {
ABModeState.IDLE -> ABModeState.WAITING_FOR_A
ABModeState.WAITING_FOR_A, ABModeState.WAITING_FOR_B -> ABModeState.IDLE
ABModeState.LOOP_ACTIVE -> {
mediaPlayer?.clearLoop()
ABModeState.IDLE
}
}
}
fun setABModeState(state: ABModeState) {
_abModeState.value = state
}
fun calculateSmartPointB(rawPointB: Int, isZeroIndexed: Boolean = false): Int {
val currentMeasure = _measure.value
val numerator = currentMeasure.substringBefore("/").toIntOrNull() ?: 4

View file

@ -93,6 +93,15 @@ actual class FMediaPlayer actual constructor(
loadSavedInstrumentsToPlayer(this)
sequencer?.addMetaEventListener { meta ->
if(meta.type == 47){
if (isLoopingAB) {
val resolution = sequencer?.sequence?.resolution?.toDouble() ?: 60.0
val gridA = (pointA / resolution).toInt()
seekToGrid(gridA)
if (sequencer?.isRunning == false) {
sequencer?.start()
}
return@addMetaEventListener
}
val totalGrids = (sequencer!!.sequence.tickLength / sequencer!!.sequence.resolution).toInt()
val currentGrid = (sequencer!!.tickPosition / sequencer!!.sequence.resolution).toInt()
val pendingDc = getPendingDcStep()
@ -979,7 +988,12 @@ actual class FMediaPlayer actual constructor(
while(isLoopingAB) {
val currentTick = sequencer?.tickPosition?: 0L
if (currentTick >= pointB) {
sequencer?.tickPosition = pointA
val resolution = sequencer?.sequence?.resolution?.toDouble() ?: 60.0
val gridA = (pointA / resolution).toInt()
seekToGrid(gridA)
if (sequencer?.isRunning == false) {
sequencer?.start()
}
}
delay(50)
}