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

View file

@ -1,6 +1,7 @@
package mg.dot.feufaro.ui package mg.dot.feufaro.ui
import SharedScreenModel import SharedScreenModel
import SharedScreenModel.ABModeState
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
@ -361,19 +362,20 @@ fun MidiControlPanel(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Column { Column {
val abModeState by sharedScreenModel.abModeState.collectAsState()
val isAtStart = loopState.first == -1L val isAtStart = loopState.first == -1L
val isWaitingForB = loopState.first != -1L && !loopState.third val isWaitingForB = abModeState == ABModeState.WAITING_FOR_B
val isLooping = loopState.third 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( OutlinedButton(
onClick = { onClick = {
when { sharedScreenModel.toggleABSelectionMode()
isAtStart -> mediaPlayer.setPointA(sharedScreenModel.activeIndex.value)
isWaitingForB -> mediaPlayer.setPointB(sharedScreenModel.activeIndex.value)
isLooping -> {
mediaPlayer.clearLoop()
}
}
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = when { containerColor = when {
@ -391,7 +393,7 @@ fun MidiControlPanel(
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
Text( Text(
text = "A", text = abButtonText,
color = if (isWaitingForB || isLooping) Color.White else Color.Black, color = if (isWaitingForB || isLooping) Color.White else Color.Black,
fontSize = MaterialTheme.typography.titleLarge.fontSize fontSize = MaterialTheme.typography.titleLarge.fontSize
) )
@ -402,12 +404,6 @@ fun MidiControlPanel(
tint = if (isLooping) Color.White else Color.Black, tint = if (isLooping) Color.White else Color.Black,
modifier = Modifier.size(16.dp) 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() val loopingState: StateFlow<LoopRange> = _loopingState.asStateFlow()
fun updateLoopingState(a: Int, b: Int) { fun updateLoopingState(a: Int, b: Int) {
updateActiveIndexByIndex(a)
_loopingState.value = LoopRange(a, b) _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 { fun calculateSmartPointB(rawPointB: Int, isZeroIndexed: Boolean = false): Int {
val currentMeasure = _measure.value val currentMeasure = _measure.value
val numerator = currentMeasure.substringBefore("/").toIntOrNull() ?: 4 val numerator = currentMeasure.substringBefore("/").toIntOrNull() ?: 4

View file

@ -93,6 +93,15 @@ actual class FMediaPlayer actual constructor(
loadSavedInstrumentsToPlayer(this) loadSavedInstrumentsToPlayer(this)
sequencer?.addMetaEventListener { meta -> sequencer?.addMetaEventListener { meta ->
if(meta.type == 47){ 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 totalGrids = (sequencer!!.sequence.tickLength / sequencer!!.sequence.resolution).toInt()
val currentGrid = (sequencer!!.tickPosition / sequencer!!.sequence.resolution).toInt() val currentGrid = (sequencer!!.tickPosition / sequencer!!.sequence.resolution).toInt()
val pendingDc = getPendingDcStep() val pendingDc = getPendingDcStep()
@ -979,7 +988,12 @@ actual class FMediaPlayer actual constructor(
while(isLoopingAB) { while(isLoopingAB) {
val currentTick = sequencer?.tickPosition?: 0L val currentTick = sequencer?.tickPosition?: 0L
if (currentTick >= pointB) { 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) delay(50)
} }