Update mode toggle, split flash visuals, and interval-ms controls
This commit is contained in:
@@ -67,7 +67,7 @@ class MainViewModel(
|
||||
|
||||
fun setMode(mode: SessionMode) = _ui.update { it.copy(config = it.config.copy(mode = mode), error = null) }
|
||||
fun setDurationMin(min: Int) = _ui.update { it.copy(config = it.config.copy(durationSec = (min.coerceIn(1, 30) * 60)), error = null) }
|
||||
fun setBlinkFrequency(value: Float) = _ui.update { it.copy(config = it.config.copy(blinkFrequencyHz = value.coerceIn(1f, 20f)), error = null) }
|
||||
fun setFlashIntervalMs(value: Int) = _ui.update { it.copy(config = it.config.copy(flashIntervalMs = value.coerceIn(50, 2000)), error = null) }
|
||||
fun setCarrier(value: Float) = _ui.update { it.copy(config = it.config.copy(carrierFrequencyHz = value.coerceIn(80f, 400f)), error = null) }
|
||||
fun setDifference(value: Float) = _ui.update { it.copy(config = it.config.copy(binauralDifferenceHz = value.coerceIn(0.5f, 20f)), error = null) }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.mindmachine.mvp.domain.SessionMode
|
||||
object SessionValidator {
|
||||
fun validate(config: SessionConfig, headsetAvailable: Boolean): String? {
|
||||
if (config.durationSec !in 60..(30 * 60)) return "Duration must be 1 to 30 minutes."
|
||||
if (config.blinkFrequencyHz !in 1f..20f) return "Blink frequency must be 1.0 to 20.0 Hz."
|
||||
if (config.flashIntervalMs !in 50..2000) return "Flash interval must be 50 to 2000 ms."
|
||||
if (config.carrierFrequencyHz !in 80f..400f) return "Carrier frequency must be 80 to 400 Hz."
|
||||
if (config.binauralDifferenceHz !in 0.5f..20f) return "Binaural difference must be 0.5 to 20 Hz."
|
||||
if (config.mode != SessionMode.VISUAL_ONLY && !headsetAvailable) {
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
package com.mindmachine.mvp.session
|
||||
|
||||
import com.mindmachine.mvp.domain.VisualPattern
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sin
|
||||
enum class FlashColor {
|
||||
RED,
|
||||
GREEN,
|
||||
BLACK,
|
||||
}
|
||||
|
||||
fun computeVisualAlpha(
|
||||
visualPattern: VisualPattern,
|
||||
data class SplitFlashFrame(
|
||||
val left: FlashColor,
|
||||
val right: FlashColor,
|
||||
)
|
||||
|
||||
fun computeSplitFlashFrame(
|
||||
frameTimeNanos: Long,
|
||||
blinkFrequencyHz: Float,
|
||||
intensityPercent: Int,
|
||||
): Float {
|
||||
val intensity = (intensityPercent.coerceIn(0, 100) / 100f)
|
||||
if (intensity <= 0f) return 0f
|
||||
|
||||
val frequency = blinkFrequencyHz.coerceIn(1f, 20f).toDouble()
|
||||
val timeSeconds = frameTimeNanos / 1_000_000_000.0
|
||||
|
||||
return when (visualPattern) {
|
||||
VisualPattern.FLASH -> {
|
||||
val phase = (timeSeconds * frequency) % 1.0
|
||||
if (phase < 0.5) intensity else 0f
|
||||
}
|
||||
|
||||
VisualPattern.PULSE -> {
|
||||
val pulse = ((sin(2.0 * PI * frequency * timeSeconds) + 1.0) / 2.0).toFloat()
|
||||
(intensity * pulse).coerceIn(0f, intensity)
|
||||
}
|
||||
flashIntervalMs: Int,
|
||||
): SplitFlashFrame {
|
||||
val intervalMs = flashIntervalMs.coerceIn(50, 2000).toLong()
|
||||
val elapsedMs = frameTimeNanos / 1_000_000L
|
||||
return when (((elapsedMs / intervalMs) % 4).toInt()) {
|
||||
0 -> SplitFlashFrame(left = FlashColor.RED, right = FlashColor.GREEN)
|
||||
1 -> SplitFlashFrame(left = FlashColor.BLACK, right = FlashColor.BLACK)
|
||||
2 -> SplitFlashFrame(left = FlashColor.GREEN, right = FlashColor.RED)
|
||||
else -> SplitFlashFrame(left = FlashColor.BLACK, right = FlashColor.BLACK)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user