Fix! A-B looping ux
This commit is contained in:
parent
bcd2d9a1f9
commit
540b508250
4 changed files with 86 additions and 37 deletions
|
|
@ -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,10 +1111,12 @@ 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)
|
||||||
|
}
|
||||||
|
ABModeState.WAITING_FOR_B -> {
|
||||||
val enableSmartB = prefs.getBoolean("smartBpoint", true)
|
val enableSmartB = prefs.getBoolean("smartBpoint", true)
|
||||||
val smartIndex = if (enableSmartB) {
|
val smartIndex = if (enableSmartB) {
|
||||||
sharedScreenModel.calculateSmartPointB(globalIndex, isZeroIndexed = true)
|
sharedScreenModel.calculateSmartPointB(globalIndex, isZeroIndexed = true)
|
||||||
|
|
@ -1118,11 +1124,15 @@ fun LazyVerticalGridTUO(
|
||||||
globalIndex
|
globalIndex
|
||||||
}
|
}
|
||||||
sharedScreenModel.mediaPlayer?.setPointB(smartIndex)
|
sharedScreenModel.mediaPlayer?.setPointB(smartIndex)
|
||||||
} else {
|
|
||||||
|
sharedScreenModel.setABModeState(ABModeState.LOOP_ACTIVE)
|
||||||
|
}
|
||||||
|
ABModeState.IDLE, ABModeState.LOOP_ACTIVE -> {
|
||||||
sharedScreenModel.seekToGrid(globalIndex)
|
sharedScreenModel.seekToGrid(globalIndex)
|
||||||
sharedScreenModel.updateActiveIndexByIndex(globalIndex)
|
sharedScreenModel.updateActiveIndexByIndex(globalIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onDoubleTap = { offset ->
|
onDoubleTap = { offset ->
|
||||||
if(editMode) {
|
if(editMode) {
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue