Auto commit (MindMachine) Mon Apr 13 08:01:01 PM CDT 2026

This commit is contained in:
Tretzi
2026-04-13 20:01:01 -05:00
parent e3ac06b047
commit 14dd04413b
12 changed files with 342 additions and 67 deletions

View File

@@ -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