Skip to content

Commit 7bb1e2b

Browse files
committed
sync all values from nestdrop OSC on startup
- improve OSC class structure to reduce duplicated code - improve XML watching and loading code
1 parent cdb28b9 commit 7bb1e2b

File tree

10 files changed

+569
-1140
lines changed

10 files changed

+569
-1140
lines changed

src/desktopMain/kotlin/Main.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ import kotlinx.coroutines.Dispatchers
3030
import kotlinx.coroutines.FlowPreview
3131
import kotlinx.coroutines.channels.consumeEach
3232
import kotlinx.coroutines.delay
33+
import kotlinx.coroutines.flow.consumeAsFlow
3334
import kotlinx.coroutines.flow.debounce
35+
import kotlinx.coroutines.flow.distinctUntilChanged
36+
import kotlinx.coroutines.flow.filterNotNull
37+
import kotlinx.coroutines.flow.flow
3438
import kotlinx.coroutines.flow.flowOn
3539
import kotlinx.coroutines.flow.launchIn
40+
import kotlinx.coroutines.flow.map
41+
import kotlinx.coroutines.flow.mapNotNull
3642
import kotlinx.coroutines.flow.merge
3743
import kotlinx.coroutines.flow.onEach
3844
import kotlinx.coroutines.launch
@@ -45,11 +51,11 @@ import nestdrop.deck.Deck
4551
import nestdrop.deck.PresetQueues
4652
import nestdrop.deck.loadDeckSettings
4753
import nestdrop.loadNestdropConfig
54+
import nestdrop.parseNestdropXml
4855
import nestdrop.setupSpriteFX
4956
import org.jetbrains.compose.resources.painterResource
50-
import osc.initializeSyncedValues
5157
import osc.runNestDropSend
52-
import osc.startNestdropListener
58+
import osc.startNestdropOSC
5359
import tags.startTagsFileWatcher
5460
import ui.App
5561
import ui.components.verticalScroll
@@ -153,14 +159,30 @@ object Main {
153159

154160
// OSCMessage("thiswillfail", "string", 'c', "" to "")
155161

156-
flowScope.launch(Dispatchers.IO) {
162+
run {
157163
logger.info { "loading nestdrop XML" }
158-
loadNestdropConfig(presetQueues, decks)
159-
nestdropConfig.asWatchChannel(KWatchChannel.Mode.SingleFile).consumeEach {
160-
if (it.kind == KWatchEvent.Kind.Modified) {
161-
loadNestdropConfig(presetQueues, decks)
164+
loadNestdropConfig(parseNestdropXml(), presetQueues, decks)
165+
166+
nestdropConfig
167+
.asWatchChannel(KWatchChannel.Mode.SingleFile)
168+
.consumeAsFlow().mapNotNull {
169+
if (it.kind == KWatchEvent.Kind.Modified) {
170+
try {
171+
parseNestdropXml()
172+
} catch (e: Exception) {
173+
logger.error(e) { "failed to load XML after file modification was detected" }
174+
null
175+
}
176+
} else {
177+
null
178+
}
162179
}
163-
}
180+
.distinctUntilChanged()
181+
.onEach {
182+
logger.info { "parsed (changed) xml from $nestdropConfig" }
183+
loadNestdropConfig(it, presetQueues, decks)
184+
}
185+
.launchIn(flowScope)
164186
}
165187

166188

@@ -207,10 +229,8 @@ object Main {
207229
// deck2.imgSprite.index.value--
208230

209231
logger.info { "starting OSC listener" }
210-
startNestdropListener()
232+
startNestdropOSC()
211233

212-
logger.info { "initializing OSC synced values" }
213-
initializeSyncedValues()
214234
delay(200)
215235
// logger.info { "re-emitting all values" }
216236
}
@@ -345,7 +365,7 @@ object Main {
345365
// transparent = true,
346366
focusable = false,
347367
alwaysOnTop = true,
348-
icon = painterResource (resource = Res.drawable.blobhai_trans)
368+
icon = painterResource(resource = Res.drawable.blobhai_trans)
349369
) {
350370
splashScreen()
351371
}
@@ -354,7 +374,7 @@ object Main {
354374
onCloseRequest = ::exitApplication,
355375
title = "Nest Ctrl",
356376
state = rememberWindowState(width = 1600.dp, height = 1200.dp),
357-
icon = painterResource (resource = Res.drawable.blobhai_trans)
377+
icon = painterResource(resource = Res.drawable.blobhai_trans)
358378
) {
359379
App()
360380
}

src/desktopMain/kotlin/beatCounter.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ val bpmSynced = OscSynced.ValueSingle<Float>(
4444
// it.logReceived = false
4545
//}
4646

47+
val beatCounter = MutableStateFlow(0.0)
48+
4749
suspend fun startBeatCounter(
4850
// vararg decks: Deck
4951
) {
@@ -89,7 +91,7 @@ suspend fun startBeatCounter(
8991

9092

9193
run {
92-
val beatCounter = MutableStateFlow(0.0)
94+
// val beatCounter = MutableStateFlow(0.0)
9395

9496
beatCounter.combine(beatFrame) { beats, beatFrame ->
9597
beats to beatFrame
@@ -102,9 +104,9 @@ suspend fun startBeatCounter(
102104
// var newBeatCounter = beatCounter.value
103105
beatCounter.value %= totalBeats
104106

105-
decks.forEach {
106-
it.presetSwitching.resetLatch()
107-
}
107+
// decks.forEach {
108+
// it.presetSwitching.resetLatch()
109+
// }
108110
}
109111
}
110112
.launchIn(flowScope)
@@ -129,10 +131,11 @@ suspend fun startBeatCounter(
129131
// }
130132
// .launchIn(flowScope)
131133

132-
decks.forEach { deck ->
133-
deck.presetSwitching.beatFlow(beatCounter.runningHistoryNotNull())
134-
.launchIn(flowScope)
135-
}
134+
// decks.forEach { deck ->
135+
// deck.presetSwitching.beatFlow(beatCounter.runningHistoryNotNull())
136+
// .launchIn(flowScope)
137+
// }
138+
136139
// decks.forEach { deck ->
137140
// deck.presetSwitching
138141
// .beatFlow(

src/desktopMain/kotlin/nestdrop/NestdropControl.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,18 @@ sealed interface NestdropControl {
101101

102102
private val valueLabel = MutableStateFlow("")
103103

104-
private val syncedFlow = OscSynced.ValueSingle(
104+
private val syncedFlow = object : OscSynced.ValueSingle<Float>(
105105
address = sliderAddress,
106106
initialValue = initialValue,
107107
target = OscSynced.Target.Nestdrop,
108-
)
108+
) {
109+
override fun convertArg(input: Any): Float =
110+
when (input) {
111+
is Float -> input
112+
is Number -> input.toFloat()
113+
else -> error("input should be a number")
114+
}
115+
}
109116

110117
suspend fun doReset() {
111118
stateFlow.value = defaultValue

src/desktopMain/kotlin/nestdrop/deck/Deck.kt

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package nestdrop.deck
33
import androidx.compose.runtime.Immutable
44
import androidx.compose.ui.graphics.Color
55
import androidx.compose.ui.graphics.compositeOver
6+
import beatCounter
67
import beatFrame
8+
import com.illposed.osc.OSCMessage
79
import configFolder
810
import decks
911
import flowScope
@@ -92,7 +94,7 @@ class Deck(
9294
val horizontalMotion = NestdropControl.SliderWithResetButton(id, "HorizonMotion", -0.5f..0.5f, 0.0f)
9395
val verticalMotion = NestdropControl.SliderWithResetButton(id, "VerticalMotion", -0.5f..0.5f, 0.0f)
9496
val stretchSpeed = NestdropControl.SliderWithResetButton(id, "StretchSpeed", 0.5f..1.5f, 1.0f)
95-
val waveMode = NestdropControl.SliderWithResetButton(id, "WaveMode", -15f..15f, 0.0f)
97+
val waveMode = NestdropControl.SliderWithResetButton(id, "WaveMode", -15f..15f, 0f)
9698

9799
suspend fun startFlows() {
98100
transitionTime.startFlows()
@@ -258,47 +260,6 @@ class Deck(
258260
private val hasSwitchedNew = MutableStateFlow(false)
259261
val hasSwitched = hasSwitchedNew.asStateFlow()
260262

261-
suspend fun resetLatch() {
262-
// hasSwitched.value = false
263-
}
264-
265-
// suspend fun beatFlowOldOld(
266-
// flow: Flow<HistoryNotNull<Double>>
267-
// ) = flow
268-
// .combine(
269-
// triggerTime.combine(beatFrame) { triggerTime, b ->
270-
// logger.info { "$deckName triggerTime: ${triggerTime * b} ($triggerTime * $b)" }
271-
// triggerTime * b
272-
// }
273-
// ) { (currentBeat, lastBeat), triggerAt ->
274-
// Triple(currentBeat, lastBeat, triggerAt)
275-
// }.onEach { (currentBeat, lastBeat, triggerAt) ->
276-
//
277-
//// logger.info { "currentBeat: $currentBeat" }
278-
//// logger.info { "triggerAt: $triggerAt" }
279-
// if (!hasSwitched.value && lastBeat < triggerAt && currentBeat >= triggerAt) {
280-
// logger.info { "$deckName triggered at ${(currentBeat * 1000).roundToInt() / 1000f} ${(triggerAt * 1000).roundToInt() / 1000f}" }
281-
// hasSwitched.value = true
282-
// doSwitch()
283-
// }
284-
// }
285-
suspend fun beatFlowOld(
286-
flow: Flow<HistoryNotNull<Double>>
287-
) = combine(
288-
flow,
289-
triggerTime.combine(beatFrame) { triggerTime, b ->
290-
logger.info { "$deckName triggerTime: ${triggerTime * b} ($triggerTime * $b)" }
291-
triggerTime * b
292-
},
293-
isEnabled
294-
) { (currentBeat, lastBeat), triggerAt, isEnabled ->
295-
if (isEnabled && !hasSwitched.value && lastBeat < triggerAt && currentBeat >= triggerAt) {
296-
logger.info { "$deckName triggered at ${(currentBeat * 1000).roundToInt() / 1000f} ${(triggerAt * 1000).roundToInt() / 1000f}" }
297-
// hasSwitched.value = true
298-
doSwitch()
299-
}
300-
}
301-
302263
suspend fun beatFlow(
303264
flow: Flow<HistoryNotNull<Double>>
304265
) = combine(
@@ -316,7 +277,9 @@ class Deck(
316277
val shouldTrigger = (lastBeat < triggerAt && currentBeat >= triggerAt) || (lastBeat > beatFrame && currentBeat >= triggerAt)
317278

318279
if(shouldTrigger) {
319-
logger.info { "$deckName triggered at $currentBeat ($triggerAt)" }
280+
flowScope.launch {
281+
logger.info { "$deckName triggered at $currentBeat ($triggerAt)" }
282+
}
320283
hasSwitchedNew.value = true
321284
flowScope.launch {
322285
val transitionTime = ndTime.transitionTime.value.toDouble().seconds
@@ -358,6 +321,36 @@ class Deck(
358321
.joinToString(", ", "{", "}") { (k, v) -> "$k: \"$v\"" }
359322
appendWarn("SKIPPED $deckName preset: \"${preset.currentPreset.value.name}\" all decks: $otherDecks")
360323
}
324+
325+
suspend fun startFlows() {
326+
combine(
327+
beatCounter.runningHistoryNotNull(),
328+
beatFrame,
329+
triggerTime,
330+
isEnabled,
331+
) { (currentBeat, lastBeat), beatFrame, triggerTime, isEnabled ->
332+
val triggerAt = triggerTime * beatFrame
333+
if (isEnabled && !hasSwitchedNew.value) {
334+
val shouldTrigger =
335+
(lastBeat < triggerAt && currentBeat >= triggerAt) || (lastBeat > beatFrame && currentBeat >= triggerAt)
336+
337+
if (shouldTrigger) {
338+
logger.info { "$deckName triggered at $currentBeat $triggerAt" }
339+
hasSwitchedNew.emit(true)
340+
flowScope.launch {
341+
val transitionTime = ndTime.transitionTime.value.toDouble().seconds
342+
delay(transitionTime)
343+
hasSwitchedNew.emit(false)
344+
}
345+
flowScope.launch {
346+
doSwitch()
347+
}
348+
}
349+
}
350+
currentBeat
351+
}
352+
.launchIn(flowScope)
353+
}
361354
}
362355

363356
val presetSwitching = PresetSwitching()
@@ -371,6 +364,7 @@ class Deck(
371364
ndStrobe.startFlows()
372365
ndAudio.startFlows()
373366
ndOutput.startFlows()
367+
presetSwitching.startFlows()
374368

375369
// transitionTime
376370
// //TODO: combine with trigger flow
@@ -450,12 +444,15 @@ class Deck(
450444

451445
@Immutable
452446
inner class Preset {
453-
val currentPreset = OscSynced.FlowCustom(
447+
val currentPreset = object: OscSynced.FlowBase<PresetData>(
454448
"/Deck$id/Preset",
455449
// initialValue = PresetData(-1, "unitialized"),
456450
target = OscSynced.Target.Nestdrop
457-
) { address, arguments ->
458-
PresetData(arguments[0] as Int, arguments[1] as String)
451+
) {
452+
override fun convertMessage(message: OSCMessage): PresetData {
453+
val input = message.arguments
454+
return PresetData(input[0] as Int, input[1] as String)
455+
}
459456
}
460457
.stateIn(flowScope, SharingStarted.Eagerly, initialValue = PresetData(-1, "unitialized"))
461458

@@ -517,19 +514,22 @@ class Deck(
517514

518515
val spoutTarget = MutableStateFlow(emptySet<SpriteKey>())
519516
val spoutStates = MutableStateFlow<Map<String, SpriteKey>>(mapOf())
520-
private val spriteStateFlow = OscSynced.FlowCustom(
517+
private val spriteStateFlow = object : OscSynced.FlowBase<SpriteData>(
521518
"/Deck$id/Sprite",
522519
// SpriteData(),
523520
target = OscSynced.Target.Nestdrop
524-
) { _, args ->
525-
SpriteData(
526-
id = args[0] as Int,
527-
name = args[1] as String,
528-
mode = ImgMode.valueOf(args[2] as String),
529-
fx = args[3] as Int,
530-
mystery = args[4] as Int,
531-
enabled = (args[5] as Int) == 1,
532-
)
521+
) {
522+
override fun convertMessage(message: OSCMessage): SpriteData {
523+
val args = message.arguments
524+
return SpriteData(
525+
id = args[0] as Int,
526+
name = args[1] as String,
527+
mode = ImgMode.valueOf(args[2] as String),
528+
fx = args[3] as Int,
529+
mystery = args[4] as Int,
530+
enabled = (args[5] as Int) == 1,
531+
)
532+
}
533533
}
534534

535535
// suspend fun enabledSprites() = imgStates.value.values
@@ -544,12 +544,11 @@ class Deck(
544544
// .launchIn(flowScope)
545545
// val imgSpriteIds = imgSpritesMap.map { it.values.map { it.id }.toSet() } //.stateIn(flowScope)
546546
spriteStateFlow
547-
.onEach {
548-
logger.info { "$deckName sprite: $it" }
549-
}
550-
547+
// .onEach {
548+
// logger.info { "$deckName sprite: $it" }
549+
// }
551550
.combine(
552-
imgSpritesMap.map { it.values.map { it.id }.toSet() }
551+
imgSpritesMap.map { map -> map.values.map { img -> img.id }.toSet() }
553552
) { spriteData, imgSpriteIds ->
554553
spriteData.copy(isImg = spriteData.id in imgSpriteIds)
555554
}

src/desktopMain/kotlin/nestdrop/loadQueues.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ private val logger = KotlinLogging.logger { }
1818
// ).trim().toInt()
1919
//}
2020

21-
private suspend fun parseNestdropXml(
21+
suspend fun parseNestdropXml(
2222
retries: Int = 0
2323
): NestdropSettings {
2424
val nestdropSettings: NestdropSettings = try {
@@ -33,7 +33,7 @@ private suspend fun parseNestdropXml(
3333
logger.error(e) { "failed to parse XML" }
3434
delay(100)
3535
if (retries < 5) {
36-
return parseNestdropXml( retries + 1)
36+
return parseNestdropXml(retries + 1)
3737
} else {
3838
throw e
3939
}
@@ -42,11 +42,12 @@ private suspend fun parseNestdropXml(
4242
}
4343

4444
suspend fun loadNestdropConfig(
45+
nestdropSettings: NestdropSettings,
4546
presetQueues: PresetQueues,
4647
decks: List<Deck>
4748
) {
48-
val nestdropSettings = parseNestdropXml()
49-
logger.info { "loaded xml from $nestdropConfig" }
49+
// val nestdropSettings = parseNestdropXml()
50+
// logger.info { "loaded xml from $nestdropConfig" }
5051

5152
// val numberOfDecks = loadNumberOfDecks()
5253
val numberOfDecks = nestdropSettings.mainWindow.settingsGeneral.nbDecks

0 commit comments

Comments
 (0)