MindMachine: track timeline model + parameter control helper
This commit is contained in:
@@ -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<Parameter>
|
||||
) {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user