diff --git a/app/src/main/java/com/mindmachine/mvp/session/TimelineModel.kt b/app/src/main/java/com/mindmachine/mvp/session/TimelineModel.kt new file mode 100644 index 0000000..ab74cfc --- /dev/null +++ b/app/src/main/java/com/mindmachine/mvp/session/TimelineModel.kt @@ -0,0 +1,92 @@ +package com.mindmachine.mvp.session + +import com.mindmachine.mvp.domain.SessionConfig +import com.mindmachine.mvp.domain.SessionMode +import com.mindmachine.mvp.domain.SessionPreset +import kotlin.math.roundToInt + +/** + * Lightweight model for the current (non-timeline) setup sliders. + * + * Note: Dana's spec introduces a full timeline editor; this model is just to keep + * the existing SetupScreen compiling while we wire the dedicated editor screen. + */ + +data class TimelineModel( + val preset: SessionPreset, + val durationSeconds: Int, + val parameters: List +) { + + data class Parameter( + val id: String, + val name: String, + val description: String, + val min: Float, + val max: Float, + val defaultValue: Float, + val value: Float, + val units: String = "" + ) + + companion object { + fun createForPreset(preset: SessionPreset, durationSeconds: Int): TimelineModel { + val flashSec = preset.flashIntervalMs.toFloat() / 1000f + val params = listOf( + Parameter( + id = "flash_interval", + name = "Flash Interval", + description = "Time between flashes in the visual pattern", + min = 0.05f, + max = 2.0f, + defaultValue = flashSec, + value = flashSec, + units = "s" + ), + Parameter( + id = "carrier_frequency", + name = "Carrier Frequency", + description = "Base audio frequency for binaural beats", + min = 80f, + max = 400f, + defaultValue = preset.carrierFrequencyHz, + value = preset.carrierFrequencyHz, + units = "Hz" + ), + Parameter( + id = "binaural_difference", + name = "Binaural Difference", + description = "Frequency difference between left/right audio channels", + min = 0.5f, + max = 20f, + defaultValue = preset.binauralDifferenceHz, + value = preset.binauralDifferenceHz, + units = "Hz" + ) + ) + return TimelineModel(preset = preset, durationSeconds = durationSeconds, parameters = params) + } + } + + fun updateParameterValue(id: String, newValue: Float): TimelineModel { + return copy(parameters = parameters.map { if (it.id == id) it.copy(value = newValue) else it }) + } + + fun toSessionConfig(mode: SessionMode): SessionConfig { + val flashIntervalMs = ((parameters.firstOrNull { it.id == "flash_interval" }?.value + ?: (preset.flashIntervalMs.toFloat() / 1000f)) * 1000f).roundToInt() + val carrier = parameters.firstOrNull { it.id == "carrier_frequency" }?.value ?: preset.carrierFrequencyHz + val diff = parameters.firstOrNull { it.id == "binaural_difference" }?.value ?: preset.binauralDifferenceHz + + return SessionConfig( + presetId = preset.id, + durationSec = durationSeconds, + mode = mode, + visualPatternType = preset.visualPatternType, + flashIntervalMs = flashIntervalMs, + intensityPercent = preset.intensityPercent, + carrierFrequencyHz = carrier, + binauralDifferenceHz = diff + ) + } +} diff --git a/app/src/main/java/com/mindmachine/mvp/session/TimelineParameterControl.kt b/app/src/main/java/com/mindmachine/mvp/session/TimelineParameterControl.kt new file mode 100644 index 0000000..8d44bab --- /dev/null +++ b/app/src/main/java/com/mindmachine/mvp/session/TimelineParameterControl.kt @@ -0,0 +1,63 @@ +package com.mindmachine.mvp.session + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt + +/** + * Temporary parameter controls (sliders) to keep SetupScreen functional. + * + * The full timeline editor per spec is implemented separately in TimelineEditorScreen. + */ + +@Composable +fun TimelineParameterControl( + model: TimelineModel, + onParameterChanged: (String, Float) -> Unit, + modifier: Modifier = Modifier, + showTimeline: Boolean = false, + onTimelineChanged: (Int) -> Unit = {} +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + model.preset.name, + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onBackground + ) + + model.parameters.forEach { p -> + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(Modifier.weight(1f)) { + Text(p.name, color = MaterialTheme.colorScheme.onBackground) + Text(p.description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onBackground) + } + Text( + when (p.units) { + "s" -> String.format("%.2f %s", p.value, p.units) + else -> "${p.value.roundToInt()} ${p.units}" + }, + color = MaterialTheme.colorScheme.onBackground + ) + } + Slider( + value = p.value, + onValueChange = { onParameterChanged(p.id, it) }, + valueRange = p.min..p.max, + modifier = Modifier.fillMaxWidth() + ) + } + } +}