Auto commit (MindMachine) Mon Apr 13 08:01:01 PM CDT 2026
This commit is contained in:
@@ -18,6 +18,7 @@ import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.background
|
||||
import solutions.tretter.mindmachine.session.DurationSliderCard
|
||||
import solutions.tretter.mindmachine.session.FlashSequenceEditor
|
||||
import solutions.tretter.mindmachine.session.SetupTimelineEditor
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
@@ -519,13 +520,10 @@ fun SetupScreen(
|
||||
onCurveGranularityChanged = vm::setCurveGranularitySec,
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = ui.config.monochromeFlashMode,
|
||||
onCheckedChange = vm::setMonochromeFlashMode,
|
||||
)
|
||||
Text("Use black/white flashes for this session", color = MaterialTheme.colorScheme.onBackground)
|
||||
}
|
||||
FlashSequenceEditor(
|
||||
sequence = ui.timeline.flashSequence,
|
||||
onSequenceChanged = { sequence -> vm.updateTimeline(ui.timeline.withFlashSequence(sequence)) },
|
||||
)
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
if (hasUnsavedChanges) {
|
||||
@@ -653,11 +651,13 @@ fun ActiveSessionScreen(vm: MainViewModel, onFinish: () -> Unit, onBackToEntry:
|
||||
SplitFlashRenderView(ctx).apply {
|
||||
setRunning(ui.runtimeState == RuntimeState.RUNNING)
|
||||
updateIntervals(ui.runtimeParams.flashOnMs, ui.runtimeParams.flashOffMs)
|
||||
setFlashSequence(ui.config.flashSequence)
|
||||
setMonochrome(ui.config.monochromeFlashMode)
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
view.updateIntervals(ui.runtimeParams.flashOnMs, ui.runtimeParams.flashOffMs)
|
||||
view.setFlashSequence(ui.config.flashSequence)
|
||||
view.setMonochrome(ui.config.monochromeFlashMode)
|
||||
view.setRunning(ui.runtimeState == RuntimeState.RUNNING)
|
||||
}
|
||||
@@ -868,6 +868,7 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
private var onMs = 167L
|
||||
private var offMs = 167L
|
||||
private var monochrome = false
|
||||
private var flashSequence = solutions.tretter.mindmachine.session.defaultFlashSequence()
|
||||
private var started = false
|
||||
private var startUptimeMs = 0L
|
||||
private var nextToggleUptimeMs = 0L
|
||||
@@ -888,11 +889,11 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
maxLatenessMs = maxOf(maxLatenessMs, latenessMs)
|
||||
}
|
||||
|
||||
phase = (phase + 1) % 4
|
||||
phase = (phase + 1) % phaseCount()
|
||||
toggleCount += 1
|
||||
invalidate()
|
||||
|
||||
val nextDuration = if (phase == 0 || phase == 2) onMs else offMs
|
||||
val nextDuration = if (isVisiblePhase(phase)) onMs else offMs
|
||||
nextToggleUptimeMs += nextDuration
|
||||
|
||||
val shouldLogToggle = toggleCount <= 20 || latenessMs > framePeriodMs() || toggleCount % 60L == 0L
|
||||
@@ -911,11 +912,19 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
}
|
||||
|
||||
fun updateIntervals(flashOnMs: Int, flashOffMs: Int) {
|
||||
rawOnMs = flashOnMs.coerceIn(50, 2000).toLong()
|
||||
rawOffMs = flashOffMs.coerceIn(50, 2000).toLong()
|
||||
val newRawOnMs = flashOnMs.coerceIn(50, 2000).toLong()
|
||||
val newRawOffMs = flashOffMs.coerceIn(50, 2000).toLong()
|
||||
val frameMs = framePeriodMs().coerceAtLeast(1L)
|
||||
onMs = snapToFrameMs(rawOnMs, frameMs)
|
||||
offMs = snapToFrameMs(rawOffMs, frameMs)
|
||||
val newOnMs = snapToFrameMs(newRawOnMs, frameMs)
|
||||
val newOffMs = snapToFrameMs(newRawOffMs, frameMs)
|
||||
|
||||
val changed = newRawOnMs != rawOnMs || newRawOffMs != rawOffMs || newOnMs != onMs || newOffMs != offMs
|
||||
if (!changed) return
|
||||
|
||||
rawOnMs = newRawOnMs
|
||||
rawOffMs = newRawOffMs
|
||||
onMs = newOnMs
|
||||
offMs = newOffMs
|
||||
|
||||
if (isRunning) {
|
||||
Log.d(
|
||||
@@ -925,7 +934,14 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
}
|
||||
|
||||
if (isRunning && started) {
|
||||
scheduleFromNow()
|
||||
val now = SystemClock.uptimeMillis()
|
||||
val nextDuration = if (isVisiblePhase(phase)) onMs else offMs
|
||||
nextToggleUptimeMs = maxOf(nextToggleUptimeMs, now + 1L)
|
||||
val remaining = (nextToggleUptimeMs - now).coerceAtLeast(1L)
|
||||
if (remaining > nextDuration) {
|
||||
nextToggleUptimeMs = now + nextDuration
|
||||
}
|
||||
scheduleNext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,6 +978,16 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setFlashSequence(sequence: List<solutions.tretter.mindmachine.session.FlashSequenceRow>) {
|
||||
val normalized = sequence.ifEmpty { solutions.tretter.mindmachine.session.defaultFlashSequence() }
|
||||
if (flashSequence == normalized) return
|
||||
flashSequence = normalized
|
||||
if (started) {
|
||||
phase %= phaseCount()
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
handler.removeCallbacks(toggleRunnable)
|
||||
super.onDetachedFromWindow()
|
||||
@@ -981,24 +1007,35 @@ private class SplitFlashRenderView(context: Context) : View(context) {
|
||||
handler.removeCallbacks(toggleRunnable)
|
||||
handler.postAtTime(toggleRunnable, nextToggleUptimeMs)
|
||||
}
|
||||
|
||||
private fun scheduleFromNow() {
|
||||
val now = SystemClock.uptimeMillis()
|
||||
nextToggleUptimeMs = now + if (phase == 0 || phase == 2) onMs else offMs
|
||||
scheduleNext()
|
||||
private fun colorsForPhase(phase: Int): Pair<Int, Int> {
|
||||
if (!isVisiblePhase(phase)) return Pair(android.graphics.Color.BLACK, android.graphics.Color.BLACK)
|
||||
if (monochrome) return Pair(android.graphics.Color.WHITE, android.graphics.Color.WHITE)
|
||||
val row = flashSequence[(phase / 2).mod(flashSequence.size)]
|
||||
val left = paletteColor(row.leftColor)
|
||||
val right = if (row.splitScreen) paletteColor(row.rightColor) else left
|
||||
return Pair(left, right)
|
||||
}
|
||||
|
||||
private fun colorsForPhase(phase: Int): Pair<Int, Int> = when (phase) {
|
||||
0 -> if (monochrome) Pair(android.graphics.Color.WHITE, android.graphics.Color.WHITE) else Pair(android.graphics.Color.RED, android.graphics.Color.GREEN)
|
||||
2 -> if (monochrome) Pair(android.graphics.Color.WHITE, android.graphics.Color.WHITE) else Pair(android.graphics.Color.GREEN, android.graphics.Color.RED)
|
||||
else -> Pair(android.graphics.Color.BLACK, android.graphics.Color.BLACK)
|
||||
private fun phaseName(phase: Int): String {
|
||||
if (!isVisiblePhase(phase)) return "BLACK"
|
||||
val row = flashSequence[(phase / 2).mod(flashSequence.size)]
|
||||
return if (row.splitScreen) {
|
||||
"${row.leftColor.name}_${row.rightColor.name}"
|
||||
} else {
|
||||
row.leftColor.name
|
||||
}
|
||||
}
|
||||
|
||||
private fun phaseName(phase: Int): String = when (phase) {
|
||||
0 -> "RED_GREEN"
|
||||
1 -> "BLACK"
|
||||
2 -> "GREEN_RED"
|
||||
else -> "BLACK"
|
||||
private fun isVisiblePhase(phase: Int): Boolean = phase % 2 == 0
|
||||
|
||||
private fun phaseCount(): Int = (flashSequence.size.coerceAtLeast(1)) * 2
|
||||
|
||||
private fun paletteColor(color: solutions.tretter.mindmachine.session.FlashPaletteColor): Int = when (color) {
|
||||
solutions.tretter.mindmachine.session.FlashPaletteColor.BLACK -> android.graphics.Color.BLACK
|
||||
solutions.tretter.mindmachine.session.FlashPaletteColor.RED -> android.graphics.Color.RED
|
||||
solutions.tretter.mindmachine.session.FlashPaletteColor.GREEN -> android.graphics.Color.GREEN
|
||||
solutions.tretter.mindmachine.session.FlashPaletteColor.YELLOW -> android.graphics.Color.YELLOW
|
||||
solutions.tretter.mindmachine.session.FlashPaletteColor.BLUE -> android.graphics.Color.BLUE
|
||||
}
|
||||
|
||||
private fun refreshRate(): Float = display?.refreshRate?.takeIf { it > 0f } ?: 60f
|
||||
|
||||
Reference in New Issue
Block a user