This commit is contained in:
Moritz Ruth 2023-05-24 01:40:38 +02:00
parent d1d326cfa8
commit 202d0ff272
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
27 changed files with 474 additions and 639 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ build/
/run/
/.idea/
/src/main/resources/ui/*

View file

@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("com.github.johnrengelman.shadow") version "7.1.2"
application
}
@ -11,7 +12,7 @@ version = "1.0-SNAPSHOT"
allprojects {
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "16"
kotlinOptions.jvmTarget = "19"
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes"
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.contracts.ExperimentalContracts"

View file

@ -1,8 +1,9 @@
package de.moritzruth.lampenfieber
import de.moritzruth.lampenfieber.device.CoemarProWash
import de.moritzruth.lampenfieber.device.FuturelightDmh160
import de.moritzruth.lampenfieber.device.SimpleDimmer
import de.moritzruth.lampenfieber.device.StairvilleTriLedBar
import de.moritzruth.lampenfieber.device.Wash
import de.moritzruth.theaterdsl.dmx.DmxAddress
import de.moritzruth.theaterdsl.dmx.EnttecOpenDmxUsb
import de.moritzruth.theaterdsl.show.StepCue
@ -12,11 +13,12 @@ import de.moritzruth.theaterdsl.value.Color
import de.moritzruth.theaterdsl.value.degrees
import de.moritzruth.theaterdsl.value.percent
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
val bar = StairvilleTriLedBar(DmxAddress(400u))
object FrontLights {
val left = listOf(SimpleDimmer(DmxAddress(1u)), SimpleDimmer(DmxAddress(2u)))
val center = listOf(SimpleDimmer(DmxAddress(4u)), SimpleDimmer(DmxAddress(5u)))
@ -27,62 +29,174 @@ object FrontLights {
val spotLeft = SimpleDimmer(DmxAddress(10u))
val spotRight = SimpleDimmer(DmxAddress(11u))
// Nebel: SF-1500
object Tops {
val left = FuturelightDmh160(DmxAddress(37u))
val right = FuturelightDmh160(DmxAddress(53u))
val both = listOf(left, right)
}
object Washs {
val left = Wash(DmxAddress(85u))
val right = Wash(DmxAddress(101u))
val all = listOf(left, right)
val left = CoemarProWash(DmxAddress(85u), false)
val right = CoemarProWash(DmxAddress(101u), true)
val both = listOf(left, right)
}
val devices = persistentSetOf(bar, *FrontLights.all.toTypedArray(), spotLeft, spotRight, *Washs.all.toTypedArray())
val bar = StairvilleTriLedBar(DmxAddress(121u)) // TODO: Change address on the device
val devices = persistentSetOf(*FrontLights.all.toTypedArray(), spotLeft, spotRight, *Tops.both.toTypedArray(), *Washs.both.toTypedArray(), bar)
@Suppress("DuplicatedCode")
val show = createShow {
act("Erster Akt") {
scene("I") {
step {
trigger = StepCue.MusicStart("Lampenfieber", 69.seconds)
scene("Intro") {
step(StepCue.MusicStart("Lampenfieber", 5.minutes + 30.seconds)) {
props {
}
onRun {
// Lichteffekte in der Aula
spotRight.brightness.static(100.percent)
Washs.both.forEach {
it.pointAtCeiling()
it.colorWheelMode.static(CoemarProWash.ColorWheelMode.White)
}
}
}
scene("I.1") {
step {
trigger = StepCue.MusicEnd
lightStep(StepCue.Custom("Gitarren-Einsatz (Takt 17)")) {
Washs.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
FrontLights.all.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds, start = 20.percent) }
Tops.both.forEach { it.startRoomMovement(5.5) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
launch {
delay(500)
Washs.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
}
Tops.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
Washs.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
FrontLights.all.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds, start = 20.percent) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
launch {
delay(500)
Washs.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
}
Tops.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
Washs.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
FrontLights.all.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds, start = 20.percent) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
launch {
delay(500)
Washs.both.forEach {
it.brightness.pulseOnce(500.milliseconds, 5.seconds)
}
}
Tops.both.forEach { it.brightness.pulseOnce(500.milliseconds, 5.seconds) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
Washs.both.forEach { it.brightness.fade(100.percent, 500.milliseconds) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
Tops.both.forEach { it.brightness.fade(100.percent, 500.milliseconds) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
FrontLights.center.forEach { it.brightness.pulseOnce(500.milliseconds, 3.seconds, end = 50.percent) }
}
lightStep(StepCue.Custom("Gitarren-Ton")) {
FrontLights.center.forEach { it.brightness.fade(0.percent, 30.seconds) }
Washs.both.forEach { it.colorWheelMode.static(CoemarProWash.ColorWheelMode.Rotate(20.percent)) }
Tops.both.forEach { it.colorWheelMode.static(FuturelightDmh160.ColorWheelMode.Rotate(20.percent)) }
}
step(StepCue.Text("Chor", "fangen wir an")) {
onRun {
FrontLights.center.forEach { it.brightness.fade(0.percent, 3.seconds) }
Washs.both.forEach { it.brightness.fade(0.percent, 3.seconds) }
Tops.both.forEach { it.brightness.fade(0.percent, 3.seconds) }
}
}
}
scene("1.1") {
step(StepCue.MusicEnd) {
actors {
+"Richy / durch den Mittelgang"
+"Christine / steht auf der Vorbühne"
+"Andreas / steht auf der Vorbühne"
+"Steffi / steht auf der Vorbühne"
+"Jakob / steht auf der Vorbühne"
+"Tina / steht auf der Vorbühne"
}
onRun {
spotRight.brightness.static(0.percent)
spotRight.brightness.fade(100.percent, 6.seconds)
FrontLights.center.forEach { it.brightness.fade(75.percent, 10.seconds) }
Washs.both.forEach {
it.pointAtStageCenter()
it.colorWheelMode.static(CoemarProWash.ColorWheelMode.White)
}
}
}
step {
trigger = StepCue.MusicStart("Rap", 69.seconds)
step(StepCue.MusicStart("Rap", 2.minutes + 30.seconds)) {
actors {
// Rapper, Tänzer
}
onRun {
// sehr viel
// Nebel, rot-orange faden
FrontLights.center.forEach { it.brightness.fade(20.percent, 10.seconds) }
bar.color.static(Color(100.degrees))
bar.brightness.fade(75.percent, 10.seconds)
bar.brightness.fade(50.percent, 10.seconds)
bar.color.fadeRandomAround(20.degrees, 10.degrees, 1500.milliseconds)
}
}
step {
trigger = StepCue.MusicEnd
lightStep(StepCue.Custom("T5 S2")) {
Washs.both.forEach {
it.brightness.static(100.percent)
it.beamAngle.sine(0.percent, 100.percent, 3.seconds)
}
}
onRun {
FrontLights.center.forEach { it.brightness.fade(0.percent, 10.seconds) }
bar.brightness.fade(0.percent, 2.seconds)
lightStep(StepCue.Custom("Schlussschlag")) {
Washs.both.forEach {
it.beamAngle.fade(100.percent, 200.milliseconds)
it.brightness.fade(0.percent, 4.seconds)
}
bar.brightness.fade(0.percent, 1.seconds)
FrontLights.center.forEach { it.brightness.pulseOnce(200.milliseconds, 4.seconds, end = 50.percent) }
}
step(StepCue.MusicEnd) {
actors {
// Rapper, Tänzer ab
}
}
}
scene("I.2") {
scene("1.2") {
step {
trigger = StepCue.Stub
@ -100,7 +214,7 @@ val show = createShow {
}
}
scene("I.3") {
scene("1.3") {
step {
trigger = StepCue.Stub
@ -111,7 +225,7 @@ val show = createShow {
}
}
scene("I.3") {
scene("1.3") {
step {
trigger = StepCue.Custom("Bühne erreicht")
@ -124,7 +238,7 @@ val show = createShow {
// David spielt: Licht links
}
scene("I.5") {
scene("1.5") {
step {
trigger = StepCue.Stub
@ -132,7 +246,7 @@ val show = createShow {
FrontLights.all.forEach { it.brightness.fade(0.percent, 10.seconds) }
FrontLights.right.forEach { it.brightness.fade(50.percent, 10.seconds) }
Washs.all.forEach {
Washs.both.forEach {
it.brightness.fade(75.percent, 10.seconds)
// Blau
}
@ -148,7 +262,7 @@ val show = createShow {
onRun {
FrontLights.right.forEach { it.brightness.fade(0.percent, 10.seconds) }
Washs.all.forEach {
Washs.both.forEach {
it.brightness.fade(100.percent, 10.seconds)
}
@ -161,13 +275,13 @@ val show = createShow {
onRun {
FrontLights.right.forEach { it.brightness.off() }
Washs.all.forEach { it.brightness.off() }
Washs.both.forEach { it.brightness.off() }
// Nebel aus
}
}
}
scene("II.1") {
scene("2.1") {
step {
trigger = StepCue.MusicStart("Computerspiel", 69.seconds)
@ -177,7 +291,7 @@ val show = createShow {
}
}
scene("II.2") {
scene("2.2") {
step {
trigger = StepCue.Stub
@ -188,7 +302,7 @@ val show = createShow {
}
}
scene("II.3") {
scene("2.3") {
step {
trigger = StepCue.Stub
@ -240,7 +354,7 @@ val show = createShow {
}
}
scene("III.1") {
scene("3.1") {
step {
trigger = StepCue.Stub
@ -253,7 +367,7 @@ val show = createShow {
}
}
scene("III.2") {
scene("3.2") {
step {
trigger = StepCue.Stub
@ -272,14 +386,14 @@ val show = createShow {
}
}
scene("III.3") {
scene("3.3") {
step {
trigger = StepCue.MusicStart("Tischballet", 69.seconds)
// Instrumental, Umbau zu Musiksaal
}
}
scene("III.4") {
scene("3.4") {
step {
trigger = StepCue.MusicEnd
// Vorhang auf
@ -298,7 +412,7 @@ val show = createShow {
}
}
scene("III.5") {
scene("3.5") {
step {
trigger = StepCue.Custom("Ende")
@ -311,7 +425,7 @@ val show = createShow {
// Umbau-Musik
}
scene("III.6") {
scene("3.6") {
step {
trigger = StepCue.Custom("Vorhang auf, Musik Ende")
@ -322,7 +436,7 @@ val show = createShow {
}
}
scene("III.7") {
scene("3.7") {
step {
trigger = StepCue.Stub
@ -333,7 +447,7 @@ val show = createShow {
}
}
scene("III.8") {
scene("3.8") {
step {
trigger = StepCue.MusicEnd
@ -372,7 +486,7 @@ val show = createShow {
// RnR entfällt
scene("IV.1") {
scene("4.1") {
step {
trigger = StepCue.MusicStart("Pause", 69.seconds)
}
@ -413,7 +527,7 @@ val show = createShow {
}
}
scene("IV.2") {
scene("4.2") {
step {
trigger = StepCue.Stub
@ -432,7 +546,7 @@ val show = createShow {
}
}
scene("IV.3") {
scene("4.3") {
step {
trigger = StepCue.Stub
@ -443,7 +557,7 @@ val show = createShow {
}
}
scene("IV.4") {
scene("4.4") {
step {
trigger = StepCue.Custom("Auftritt David")
@ -475,7 +589,7 @@ val show = createShow {
}
}
scene("IV.5") {
scene("4.5") {
step {
trigger = StepCue.Stub
@ -493,7 +607,7 @@ val show = createShow {
}
}
scene("IV.6") {
scene("4.6") {
step {
trigger = StepCue.MusicEnd
@ -516,7 +630,7 @@ val show = createShow {
}
}
scene("IV.7") {
scene("4.7") {
step {
trigger = StepCue.Stub
@ -545,7 +659,7 @@ val show = createShow {
}
}
scene("IV.8") {
scene("4.8") {
step {
trigger = StepCue.MusicStart("Angstballet", 69.seconds)
@ -559,7 +673,7 @@ val show = createShow {
}
}
scene("IV.9") {
scene("4.9") {
step {
trigger = StepCue.Stub
@ -577,7 +691,7 @@ val show = createShow {
}
}
scene("IV.10") {
scene("4.10") {
step {
trigger = StepCue.Stub

View file

@ -0,0 +1,92 @@
package de.moritzruth.lampenfieber.device
import de.moritzruth.theaterdsl.device.AngleDV
import de.moritzruth.theaterdsl.device.ConcreteDV
import de.moritzruth.theaterdsl.device.Device
import de.moritzruth.theaterdsl.device.PercentageDV
import de.moritzruth.theaterdsl.dmx.DmxAddress
import de.moritzruth.theaterdsl.dmx.DmxDataWriter
import de.moritzruth.theaterdsl.dmx.DmxValue
import de.moritzruth.theaterdsl.value.Percentage
import de.moritzruth.theaterdsl.value.degrees
import de.moritzruth.theaterdsl.value.percent
import kotlinx.collections.immutable.persistentSetOf
import kotlin.math.roundToInt
class CoemarProWash(override val firstChannel: DmxAddress, private val isRight: Boolean) : Device {
companion object {
private const val PAN_LIMIT = 530f // degrees
private const val TILT_LIMIT = 284f // degrees
}
override val numberOfChannels: UInt = 16u
override fun writeDmxData(writer: DmxDataWriter) {
val pan = (pan.getCurrentValue().degrees.mod(PAN_LIMIT) / PAN_LIMIT * 65536).roundToInt().toUShort()
val tilt = (tilt.getCurrentValue().degrees.mod(TILT_LIMIT) / TILT_LIMIT * 65536).roundToInt().toUShort()
writer.writeHighByte(pan)
writer.writeHighByte(tilt)
writer.writeLowByte(pan)
writer.writeLowByte(tilt)
writer.writeRaw(DmxValue(0u)) // pan/tilt speed = maximum
writer.writeRaw(DmxValue(255u)) // fan speed and lamp control = no function
writer.writeRaw(colorWheelMode.getCurrentValue().getDmxValue())
writer.writePercentage(cyan.getCurrentValue())
writer.writePercentage(magenta.getCurrentValue())
writer.writePercentage(yellow.getCurrentValue())
writer.writeRaw(DmxValue(0u)) // dimmer and color speed = maximum
writer.writeRaw(DmxValue(0u)) // colour macro = off
writer.writeRaw(DmxValue((beamAngle.getCurrentValue().value * 109 + 71).roundToInt().toUByte()))
writer.writeRaw(DmxValue(0u)) // no function
writer.writeRaw(DmxValue((strobeSpeed.getCurrentValue().value * 32 + 63).roundToInt().toUByte()))
writer.writePercentage(brightness.getCurrentValue())
}
sealed interface ColorWheelMode {
fun getDmxValue(): DmxValue
sealed class Simple(dmxValue: Int) : ColorWheelMode {
val dmxValue = DmxValue(dmxValue.toUByte())
override fun getDmxValue() = dmxValue
}
class Rotate(val speed: Percentage, val backwards: Boolean = false) : ColorWheelMode {
override fun getDmxValue() = run {
if (backwards) ((1f - speed.value) * 62).roundToInt() + 193
else (speed.value * 62).roundToInt() + 128
}.let { DmxValue(it.toUByte()) }
}
object White : Simple(0)
object Red : Simple(20)
object DarkBlue : Simple(48)
object Green : Simple(70)
object WarmWhite : Simple(81)
object Violet : Simple(118)
}
val pan = AngleDV()
val tilt = AngleDV()
val brightness = PercentageDV()
val beamAngle = PercentageDV(100.percent)
val strobeSpeed = PercentageDV(0.percent)
val colorWheelMode = ConcreteDV<ColorWheelMode>(ColorWheelMode.White)
val cyan = PercentageDV()
val magenta = PercentageDV()
val yellow = PercentageDV()
override val dvs = persistentSetOf(
pan, tilt, brightness, beamAngle, strobeSpeed, colorWheelMode, cyan, magenta, yellow
)
fun pointAtStageCenter() {
tilt.static(80.degrees)
pan.static(if (isRight) 270.degrees else 90.degrees)
}
fun pointAtCeiling() {
tilt.static(180.degrees)
pan.static(0.degrees)
}
}

View file

@ -1,13 +1,18 @@
package de.moritzruth.lampenfieber.device
import de.moritzruth.theaterdsl.device.AngleDV
import de.moritzruth.theaterdsl.device.ConcreteDV
import de.moritzruth.theaterdsl.device.Device
import de.moritzruth.theaterdsl.device.PercentageDV
import de.moritzruth.theaterdsl.dmx.DmxAddress
import de.moritzruth.theaterdsl.dmx.DmxDataWriter
import de.moritzruth.theaterdsl.dmx.get16BitChannelValues
import de.moritzruth.theaterdsl.dmx.DmxValue
import de.moritzruth.theaterdsl.value.Percentage
import de.moritzruth.theaterdsl.value.degrees
import kotlinx.collections.immutable.persistentSetOf
import kotlin.math.roundToInt
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class FuturelightDmh160(override val firstChannel: DmxAddress) : Device {
companion object {
@ -18,22 +23,72 @@ class FuturelightDmh160(override val firstChannel: DmxAddress) : Device {
override val numberOfChannels: UInt = 16u
override fun writeDmxData(writer: DmxDataWriter) {
val fullPan = (pan.getCurrentValue().degrees.mod(PAN_LIMIT) / PAN_LIMIT * 65536).roundToInt()
val (mPan, lPan) = fullPan.get16BitChannelValues()
val pan = (pan.getCurrentValue().degrees.mod(PAN_LIMIT) / PAN_LIMIT * 65536).roundToInt().toUShort()
val tilt = (tilt.getCurrentValue().degrees.mod(TILT_LIMIT) / TILT_LIMIT * 65536).roundToInt().toUShort()
val fullTilt = (tilt.getCurrentValue().degrees.mod(TILT_LIMIT) / TILT_LIMIT * 65536).roundToInt()
val (mTilt, lTilt) = fullTilt.get16BitChannelValues()
writer.writeRaw(mPan)
writer.writeRaw(lPan)
writer.writeRaw(mTilt)
writer.writeRaw(lTilt)
writer.writeHighByte(pan)
writer.writeLowByte(pan)
writer.writeHighByte(tilt)
writer.writeLowByte(tilt)
writer.writeRaw(DmxValue(0u)) // pan/tilt speed
writer.writeRaw(DmxValue(0u)) // blackout while moving = off
writer.writeRaw(DmxValue((strobeSpeed.getCurrentValue().value * 32 + 63).roundToInt().toUByte()))
writer.writePercentage(brightness.getCurrentValue())
writer.writeRaw(colorWheelMode.getCurrentValue().getDmxValue())
writer.writeRaw(DmxValue(0u)) // gobo wheel = open
writer.writeRaw(DmxValue(0u)) // gobo wheel = open
writer.writeRaw(prismMode.getCurrentValue().dmxValue)
writer.writeRaw(DmxValue((prismRotationSpeed.getCurrentValue().value * 123 + 4).roundToInt().toUByte()))
writer.writeRaw(DmxValue(255u)) // focus = maximum distance
writer.writeRaw(DmxValue(0u)) // iris = ?
writer.writeRaw(DmxValue(0u)) // functions = noop
}
sealed interface ColorWheelMode {
fun getDmxValue(): DmxValue
sealed class Simple(dmxValue: Int) : ColorWheelMode {
val dmxValue = DmxValue(dmxValue.toUByte())
override fun getDmxValue() = dmxValue
}
class Rotate(val speed: Percentage, val backwards: Boolean = false) : ColorWheelMode {
override fun getDmxValue() = run {
if (backwards) (speed.value * 15).roundToInt() + 240
else (speed.value * 15).roundToInt() + 224
}.let { DmxValue(it.toUByte()) }
}
object White : Simple(0)
object Pink : Simple(0)
object Red : Simple(0)
object Orange : Simple(0)
object Green : Simple(0)
object LightBlue : Simple(0)
object DarkBlue : Simple(0)
object Violet : Simple(0)
}
enum class PrismMode(val dmxValue: DmxValue) {
OPEN(DmxValue(0u)),
FACETS_3(DmxValue(64u)),
FACETS_8(DmxValue(128u)),
FROST(DmxValue(192u))
}
val pan = AngleDV()
val tilt = AngleDV()
val brightness = PercentageDV()
val colorWheelMode = ConcreteDV<ColorWheelMode>(ColorWheelMode.White)
val strobeSpeed = PercentageDV()
val prismMode = ConcreteDV(PrismMode.OPEN)
val prismRotationSpeed = PercentageDV()
override val dvs = persistentSetOf(brightness)
fun startRoomMovement(rotationsPerMinute: Double) {
pan.sine(0.degrees, 360.degrees, 1.minutes / rotationsPerMinute)
tilt.fade(120.degrees, 3.seconds)
}
override val dvs = persistentSetOf(
pan, tilt, brightness, colorWheelMode, strobeSpeed
)
}

View file

@ -1,67 +0,0 @@
package de.moritzruth.lampenfieber.device
import de.moritzruth.theaterdsl.device.AngleDV
import de.moritzruth.theaterdsl.device.Device
import de.moritzruth.theaterdsl.device.PercentageDV
import de.moritzruth.theaterdsl.dmx.DmxAddress
import de.moritzruth.theaterdsl.dmx.DmxDataWriter
import de.moritzruth.theaterdsl.dmx.DmxValue
import de.moritzruth.theaterdsl.dmx.get16BitChannelValues
import de.moritzruth.theaterdsl.value.percent
import kotlinx.collections.immutable.persistentSetOf
import kotlin.math.roundToInt
class Wash(override val firstChannel: DmxAddress) : Device {
companion object {
private const val PAN_LIMIT = 630f // degrees
private const val TILT_LIMIT = 270f // degrees
}
override val numberOfChannels: UInt = 16u
override fun writeDmxData(writer: DmxDataWriter) {
val fullPan = (pan.getCurrentValue().degrees.mod(PAN_LIMIT) / PAN_LIMIT * 65536).roundToInt()
val (mPan, lPan) = fullPan.get16BitChannelValues()
val fullTilt = (tilt.getCurrentValue().degrees.mod(TILT_LIMIT) / TILT_LIMIT * 65536).roundToInt()
val (mTilt, lTilt) = fullTilt.get16BitChannelValues()
writer.writeRaw(DmxValue(130u))
writer.writeRaw(DmxValue(200u))
writer.writeRaw(lPan)
writer.writeRaw(lTilt)
writer.writeRaw(DmxValue(0u))
writer.writeRaw(DmxValue(0u))
writer.writePercentage(white.getCurrentValue())
writer.writePercentage(cyan.getCurrentValue())
writer.writePercentage(magenta.getCurrentValue())
writer.writePercentage(yellow.getCurrentValue())
writer.writeRaw(DmxValue(0u))
writer.writeRaw(DmxValue(0u))
writer.writePercentage(beamSize.getCurrentValue())
writer.writeRaw(DmxValue(0u))
writer.writeRaw(DmxValue(47u))
writer.writePercentage(brightness.getCurrentValue())
}
val pan = AngleDV()
val tilt = AngleDV()
val brightness = PercentageDV()
val white = PercentageDV()
val cyan = PercentageDV()
val magenta = PercentageDV()
val yellow = PercentageDV()
val beamSize = PercentageDV(100.percent)
override val dvs = persistentSetOf(
pan,
tilt,
brightness,
white,
cyan,
magenta,
yellow,
beamSize
)
}

View file

@ -8,6 +8,7 @@ import kotlin.math.PI
import kotlin.math.asin
import kotlin.math.min
import kotlin.math.sin
import kotlin.random.Random
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
@ -40,10 +41,13 @@ abstract class FloatDV<T>(private val initialStaticValue: Float) : DynamicValue<
}
data class Step(val steps: ImmutableList<Float>, val interval: Duration, val startIndex: Int) : State
data class PulseOnce(val rampUpDuration: Duration, val rampDownDuration: Duration, val peakValue: Float, val start: Float, val end: Float) : State
}
protected abstract fun toDomain(value: Float): T
protected abstract fun fromDomain(value: T): Float
protected abstract val minimumValue: T
protected abstract val maximumValue: T
private var stateChangeMark = TimeSource.Monotonic.markNow()
private var state: State = State.Static(initialStaticValue)
@ -74,6 +78,18 @@ abstract class FloatDV<T>(private val initialStaticValue: Float) : DynamicValue<
val index = (elapsedTime / s.interval).toInt() + s.startIndex
s.steps[index.mod(s.steps.size)]
}
is State.PulseOnce -> {
if (elapsedTime <= s.rampUpDuration) {
val progress = elapsedTime / s.rampUpDuration
val delta = s.peakValue - s.start
(progress * delta + s.start).toFloat()
} else {
val progress = min(elapsedTime / s.rampDownDuration, 1.0)
val delta = s.end - s.peakValue
(progress * delta + s.end).toFloat()
}
}
}
return toDomain(float)
@ -107,16 +123,44 @@ abstract class FloatDV<T>(private val initialStaticValue: Float) : DynamicValue<
steps(persistentListOf(a, b), interval, startIndex)
}
fun pulseOnce(rampUpDuration: Duration, rampDownDuration: Duration, peakValue: T = maximumValue, end: T = minimumValue, start: T = getCurrentValue()) {
state = State.PulseOnce(rampUpDuration, rampDownDuration, fromDomain(peakValue), fromDomain(start), fromDomain(end))
}
}
class PercentageDV(initialStaticValue: Percentage = 0.percent) : FloatDV<Percentage>(initialStaticValue.value) {
override fun fromDomain(value: Percentage): Float = value.value
override fun toDomain(value: Float): Percentage = Percentage(value)
override val minimumValue: Percentage = 0.percent
override val maximumValue: Percentage = 100.percent
}
class AngleDV(initialStaticValue: Angle = 0.degrees) : FloatDV<Angle>(initialStaticValue.degrees) {
override fun fromDomain(value: Angle): Float = value.degrees
override fun toDomain(value: Float): Angle = Angle(value)
override val minimumValue: Angle = 0.degrees
override val maximumValue: Angle = 360.degrees
}
class ConcreteDV<T>(private val initialStaticValue: T) : DynamicValue<T> {
sealed interface State<T> {
class Static<T>(val value: T) : State<T>
}
var state: State<T> = State.Static(initialStaticValue)
override fun reset() {
state = State.Static(initialStaticValue)
}
override fun getCurrentValue(): T = when (val s = state) {
is State.Static -> s.value
}
fun static(value: T) {
state = State.Static(value)
}
}
@OptIn(ExperimentalTime::class)
@ -128,6 +172,8 @@ class ColorDV(private val initialStaticValue: Color = Color.WHITE) : DynamicValu
val deltaSaturation = end.saturation.value - start.saturation.value
val deltaBrightness = end.brightness.value - start.brightness.value
}
data class FadeRandomAround(val hue: Angle, val deviation: Angle, val interval: Duration) : State
}
private var stateChangeMark = TimeSource.Monotonic.markNow()
@ -155,6 +201,11 @@ class ColorDV(private val initialStaticValue: Color = Color.WHITE) : DynamicValu
brightness = Percentage((s.start.brightness.value + s.deltaBrightness * progress).toFloat())
)
}
is State.FadeRandomAround -> {
val random = Random((elapsedTime / s.interval).toInt())
Color(hue = Angle(s.hue.degrees - (s.deviation.degrees / 2) + random.nextFloat() * s.deviation.degrees))
}
}
}
@ -165,4 +216,8 @@ class ColorDV(private val initialStaticValue: Color = Color.WHITE) : DynamicValu
fun fade(end: Color, duration: Duration, start: Color = getCurrentValue()) {
state = State.Fade(start, end, duration)
}
fun fadeRandomAround(hue: Angle, deviation: Angle, interval: Duration) {
state = State.FadeRandomAround(hue, deviation, interval)
}
}

View file

@ -12,4 +12,7 @@ interface DmxDataWriter {
*/
fun writeInRange(range: ClosedFloatingPointRange<Float>, value: Float, startAtOne: Boolean = false) =
writeRaw(Percentage((value - range.start) / (range.endInclusive - range.start)).roundToDmxValue(startAtOne))
fun writeHighByte(value: UShort) = writeRaw(DmxValue(value.toUInt().shr(8).toUByte()))
fun writeLowByte(value: UShort) = writeRaw(DmxValue(value.toUByte()))
}

View file

@ -18,8 +18,3 @@ value class DmxValue(val value: UByte) : Comparable<UByte> {
fun Percentage.roundToDmxValue(startAtOne: Boolean = false): DmxValue =
if (startAtOne) DmxValue(((value * (DmxValue.VALUE_RANGE.last.toFloat() - 1f)).roundToInt() + 1).toUByte())
else DmxValue((value * DmxValue.VALUE_RANGE.last.toFloat()).roundToInt().toUByte())
fun Int.get16BitChannelValues(): Pair<DmxValue, DmxValue> = Pair(
DmxValue((this.rotateRight(7) and 0x11111111).toUByte()),
DmxValue((this and 0x11111111).toUByte())
)

View file

@ -66,8 +66,7 @@ object EnttecOpenDmxUsb {
Thread.sleep(23)
}
} catch (exception: SerialPortIOException) {
exception.printStackTrace()
port.closePort()
// only thrown when the program is stopped
}
}

View file

@ -22,11 +22,13 @@ interface ActBuilderContext {
@TheaterDslMarker
interface SceneBuilderContext {
fun step(build: StepDataBuilderContext.() -> Unit)
fun step(cue: StepCue = StepCue.Stub, build: StepDataBuilderContext.() -> Unit)
fun lightStep(cue: StepCue, runner: StepRunner)
}
@TheaterDslMarker
interface StepDataBuilderContext {
@Deprecated("")
var trigger: StepCue
val props: PropsBuilderMap
@ -88,8 +90,13 @@ private fun buildAct(actIndex: Int, name: String, build: ActBuilderContext.() ->
val steps = mutableListOf<Step>()
object : SceneBuilderContext {
override fun step(build: StepDataBuilderContext.() -> Unit) {
var nullableTrigger: StepCue? = null
override fun lightStep(cue: StepCue, runner: StepRunner) {
step(cue) {
onRun(runner)
}
}
override fun step(cue: StepCue, build: StepDataBuilderContext.() -> Unit) {
val changedProps = mutableMapOf<PropPosition, StringWithDetails?>()
val actorEntrances = mutableSetOf<StringWithDetails>()
val actorExits = mutableSetOf<StringWithDetails>()
@ -97,10 +104,8 @@ private fun buildAct(actIndex: Int, name: String, build: ActBuilderContext.() ->
object : StepDataBuilderContext {
override var trigger: StepCue
get() = nullableTrigger ?: throw IllegalStateException("trigger was not set yet")
set(value) {
nullableTrigger = value
}
get() = cue
set(value) {}
override val props = PropsBuilderMap(changedProps)
@ -113,9 +118,7 @@ private fun buildAct(actIndex: Int, name: String, build: ActBuilderContext.() ->
}
}.build()
@Suppress("KotlinConstantConditions")
val trigger = nullableTrigger ?: throw IllegalStateException("No trigger was specified")
val logger = KotlinLogging.logger("createAct / $name / #${steps.size + 1} ${trigger.format()}")
val logger = KotlinLogging.logger("createAct / $name / #${steps.size + 1} ${cue.format()}")
val actorEntrancesNames = actorEntrances.map { it.main }
val actorExitsNames = actorExits.map { it.main }
@ -152,7 +155,7 @@ private fun buildAct(actIndex: Int, name: String, build: ActBuilderContext.() ->
steps.add(
Step(
ShowPosition(actIndex, scenes.size, steps.size),
trigger,
cue,
actorEntrances.toImmutableSet(),
actorExits.toImmutableSet(),
actorsOnStage.toImmutableList(),

View file

@ -141,7 +141,7 @@ fun CoroutineScope.startStepRunning(context: ShowContext) = launch {
val step = context.show.acts[lastPosition]
lastStepJob?.cancelAndJoin()
lastStepJob = launch(SupervisorJob(currentCoroutineContext().job)) {
lastStepJob = launch(SupervisorJob(currentCoroutineContext().job), CoroutineStart.UNDISPATCHED) {
val runContext = object : StepRunContext, CoroutineScope by this {}
step.runner?.let { runContext.it() }
@ -170,6 +170,7 @@ private fun CoroutineScope.startWebsocketServer(context: ShowContext) = launch(D
}
val showJson = Json.encodeToString(context.show)
val indexHtmlContent = this::class.java.getResourceAsStream("/ui/index.html")!!.reader().use { it.readText() }
embeddedServer(CIO, port = 8000) {
install(WebSockets) {
@ -227,9 +228,13 @@ private fun CoroutineScope.startWebsocketServer(context: ShowContext) = launch(D
}
}
staticResources("/", "ui") {
staticResources("/assets", "ui/assets") {
enableAutoHeadResponse()
}
get("/{...}") {
call.respondText(indexHtmlContent, ContentType.Text.Html, HttpStatusCode.OK)
}
}
}.start(wait = true)
}

View file

@ -5,8 +5,7 @@
"license": "Apache-2.0",
"scripts": {
"dev": "vite --port 3000 --host",
"build": "vite build",
"start": "vite preview --port 3000 --host"
"build": "vite build --emptyOutDir --outDir ../src/main/resources/ui"
},
"devDependencies": {
"@iconify-json/ph": "^1.1.5",

View file

@ -1,17 +0,0 @@
{
"main": "index.js",
"scripts": {
"dev": "tsx watch src/main.ts"
},
"devDependencies": {
"@types/node": "^18.0.1",
"@types/ws": "^8.5.3",
"tsx": "^3.7.1",
"typescript": "^4.7.4"
},
"dependencies": {
"bufferutil": "^4.0.6",
"utf-8-validate": "^5.0.9",
"ws": "^8.8.0"
}
}

344
ui/server/pnpm-lock.yaml generated
View file

@ -1,344 +0,0 @@
lockfileVersion: 5.4
specifiers:
'@types/node': ^18.0.1
'@types/ws': ^8.5.3
bufferutil: ^4.0.6
tsx: ^3.7.1
typescript: ^4.7.4
utf-8-validate: ^5.0.9
ws: ^8.8.0
dependencies:
bufferutil: 4.0.6
utf-8-validate: 5.0.9
ws: 8.8.0_22kvxa7zeyivx4jp72v2w3pkvy
devDependencies:
'@types/node': 18.0.1
'@types/ws': 8.5.3
tsx: 3.7.1
typescript: 4.7.4
packages:
/@esbuild-kit/cjs-loader/2.3.0:
resolution: {integrity: sha512-KInrVt8wlKLhWy7+y4a+E+0uBJoWgdx6Xupy+rrF4MFHA/dEt22ACvvChOZSyiqtQieYPtbPkVYSjbC7mOrFVw==}
dependencies:
'@esbuild-kit/core-utils': 2.0.2
get-tsconfig: 4.1.0
dev: true
/@esbuild-kit/core-utils/2.0.2:
resolution: {integrity: sha512-clNYQUsqtc36pzW5EufMsahcbLG45EaW3YDyf0DlaS0eCMkDXpxIlHwPC0rndUwG6Ytk9sMSD5k1qHbwYEC/OQ==}
dependencies:
esbuild: 0.14.48
source-map-support: 0.5.21
dev: true
/@esbuild-kit/esm-loader/2.4.0:
resolution: {integrity: sha512-zS720jXh06nfg5yAzm6oob4sWN9VTP2E1SonhFgEb6zCBswa4S8fOQ/4Bksz1flDgn56NPqoTTDn2XmWRyMG9Q==}
dependencies:
'@esbuild-kit/core-utils': 2.0.2
get-tsconfig: 4.1.0
dev: true
/@types/node/18.0.1:
resolution: {integrity: sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg==}
dev: true
/@types/ws/8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
'@types/node': 18.0.1
dev: true
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
/bufferutil/4.0.6:
resolution: {integrity: sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==}
engines: {node: '>=6.14.2'}
requiresBuild: true
dependencies:
node-gyp-build: 4.5.0
dev: false
/esbuild-android-64/0.14.48:
resolution: {integrity: sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-android-arm64/0.14.48:
resolution: {integrity: sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-64/0.14.48:
resolution: {integrity: sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-arm64/0.14.48:
resolution: {integrity: sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-64/0.14.48:
resolution: {integrity: sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-arm64/0.14.48:
resolution: {integrity: sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-32/0.14.48:
resolution: {integrity: sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-64/0.14.48:
resolution: {integrity: sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm/0.14.48:
resolution: {integrity: sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm64/0.14.48:
resolution: {integrity: sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-mips64le/0.14.48:
resolution: {integrity: sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-ppc64le/0.14.48:
resolution: {integrity: sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-riscv64/0.14.48:
resolution: {integrity: sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-s390x/0.14.48:
resolution: {integrity: sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-netbsd-64/0.14.48:
resolution: {integrity: sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-openbsd-64/0.14.48:
resolution: {integrity: sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-sunos-64/0.14.48:
resolution: {integrity: sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-32/0.14.48:
resolution: {integrity: sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-64/0.14.48:
resolution: {integrity: sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-arm64/0.14.48:
resolution: {integrity: sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild/0.14.48:
resolution: {integrity: sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
esbuild-android-64: 0.14.48
esbuild-android-arm64: 0.14.48
esbuild-darwin-64: 0.14.48
esbuild-darwin-arm64: 0.14.48
esbuild-freebsd-64: 0.14.48
esbuild-freebsd-arm64: 0.14.48
esbuild-linux-32: 0.14.48
esbuild-linux-64: 0.14.48
esbuild-linux-arm: 0.14.48
esbuild-linux-arm64: 0.14.48
esbuild-linux-mips64le: 0.14.48
esbuild-linux-ppc64le: 0.14.48
esbuild-linux-riscv64: 0.14.48
esbuild-linux-s390x: 0.14.48
esbuild-netbsd-64: 0.14.48
esbuild-openbsd-64: 0.14.48
esbuild-sunos-64: 0.14.48
esbuild-windows-32: 0.14.48
esbuild-windows-64: 0.14.48
esbuild-windows-arm64: 0.14.48
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/get-tsconfig/4.1.0:
resolution: {integrity: sha512-bhshxJhpfmeQ8x4fAvDqJV2VfGp5TfHdLpmBpNZZhMoVyfIrOippBW4mayC3DT9Sxuhcyl56Efw61qL28hG4EQ==}
dev: true
/node-gyp-build/4.5.0:
resolution: {integrity: sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==}
hasBin: true
dev: false
/source-map-support/0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
dev: true
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
dev: true
/tsx/3.7.1:
resolution: {integrity: sha512-dwl1GBdkwVQ9zRxTmETGi+ck8pewNm2QXh+HK6jHxdHmeCjfCL+Db3b4VX/dOMDSS2hle1j5LzQoo8OpVXu6XQ==}
hasBin: true
dependencies:
'@esbuild-kit/cjs-loader': 2.3.0
'@esbuild-kit/core-utils': 2.0.2
'@esbuild-kit/esm-loader': 2.4.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/typescript/4.7.4:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/utf-8-validate/5.0.9:
resolution: {integrity: sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==}
engines: {node: '>=6.14.2'}
requiresBuild: true
dependencies:
node-gyp-build: 4.5.0
dev: false
/ws/8.8.0_22kvxa7zeyivx4jp72v2w3pkvy:
resolution: {integrity: sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dependencies:
bufferutil: 4.0.6
utf-8-validate: 5.0.9
dev: false

View file

@ -1,46 +0,0 @@
import { WebSocketServer } from "ws"
const server = new WebSocketServer({
clientTracking: true,
host: "0.0.0.0",
port: 8000
})
let state = {
music: null,
message: "",
position: {
scene: 0,
step: 0
}
}
server.on("connection", (client, request) => {
const address = request.connection.remoteAddress
console.log(`Connected: ${address}`)
client.send(JSON.stringify({
state,
timestamp: Date.now()
}))
client.on("message", rawData => {
state = JSON.parse(rawData.toString())
console.log("Update: ", state)
server.clients.forEach(c => {
if (c !== client) {
c.send(JSON.stringify({
state,
timestamp: Date.now()
}))
}
})
})
client.on("close", () => {
console.log(`Disconnected: ${address}`)
})
})
console.log("Listening on ws://0.0.0.0:8000")

View file

@ -1,7 +1,7 @@
<template>
<div class="flex items-start space-x-2">
<component :is="icon" class="text-4 mt-0.5 flex-shrink-0"/>
<div class="text-4" :class="singleLine && 'truncate'">
<div class="flex items-start space-x-2" :class="textClass ?? 'text-4'">
<component :is="icon" class="mt-0.5 flex-shrink-0"/>
<div :class="singleLine && 'truncate'">
{{ text }}
</div>
</div>
@ -24,6 +24,7 @@
const props = defineProps<{
step: Step,
singleLine?: boolean
textClass?: string
}>()
const icon = computed(() => {

View file

@ -4,8 +4,8 @@
v-for="(scene, sceneIndex) in scenes"
:key="sceneIndex"
>
<div class="text-gray-400 pl-3">
{{ scene.name }}
<div class="text-gray-400 pl-3 text-5">
Szene {{ scene.name }}
</div>
<div class="flex flex-col">
<template v-for="step in scene.steps" :key="step.position">
@ -22,7 +22,7 @@
</template>
<script setup lang="ts">
import { show } from "../state"
import { Scene, show, ShowPosition, Step } from "../state"
import { computed } from "vue"
import MotionsListStep from "./MotionsListStep.vue"
@ -31,5 +31,41 @@
scrollable?: boolean
}>()
const scenes = computed(() => show.value.acts.flatMap(a => a.scenes))
interface MotionStep extends Step {
morePositions: ShowPosition[]
}
interface MotionScene extends Scene {
steps: MotionStep[]
}
const scenes = computed(() => {
const all = show.value.acts.flatMap(a => a.scenes)
const result: MotionScene[] = []
for (const scene of all) {
const steps: MotionStep[] = []
let accStep: MotionStep | null = null
for (const step of scene.steps) {
if (step.actorEntrances.length > 0 || step.actorExits.length > 0) {
if (accStep !== null) steps.push(accStep)
accStep = {
...step,
morePositions: []
}
} else {
if (accStep !== null) accStep.morePositions.push(step.position)
}
}
if (steps.length > 0) result.push({
...scene,
steps: steps
})
}
return result
})
</script>

View file

@ -2,8 +2,8 @@
<div class="transition p-3" :class="isActive && 'bg-green-800'">
<div class="flex space-x-2">
<div class="flex-grow">
<CueBox :step="step"/>
<div class="py-2 pl-8 space-y-2 text-6">
<CueBox text-class="text-7" :step="step"/>
<div class="py-2 pl-8 space-y-2 text-7">
<div class="flex flex-col space-y-1">
<div
v-for="motion in step.actorEntrances"
@ -47,7 +47,7 @@
</template>
<script setup lang="ts">
import { parseStringWithDetails, START_STEP, state, Step } from "../state"
import { parseStringWithDetails, ShowPosition, START_STEP, state, Step } from "../state"
import CueBox from "./CueBox.vue"
import CaretDoubleRightIcon from "virtual:icons/ph/caret-double-right"
import CaretDoubleLeftIcon from "virtual:icons/ph/caret-double-left"
@ -58,12 +58,14 @@
const props = defineProps<{
step: Step,
morePositions?: ShowPosition[]
centerCurrent: boolean
}>()
const position = toRef(state, "position")
const element = useCurrentElement()
const isActive = computed(() => isEqual(props.step.position, position.value))
const allPositions = computed(() => [props.step.position, ...(props.morePositions ?? [])])
const isActive = computed(() => allPositions.value.some(p => isEqual(p, position.value)))
watchEffect(() => {
const p = props.step.position

View file

@ -4,7 +4,7 @@
:class="scene === current.scene ? 'bg-green-900' : ''"
>
<div class="pb-1 pt-4 px-4 text-4 font-bold">
{{ scene.name }}
Szene {{ scene.name }}
</div>
<div class="flex flex-col pb-2">
<StepSelectionStep v-for="step in scene.steps" :key="step.position" :step="step"/>

View file

@ -4,10 +4,7 @@
:class="isActive ? 'bg-green-700' : ''"
>
<CueBox :step="step"/>
<button
class="flex items-center text-4"
@click="goToPosition(step.position)"
>
<button class="flex items-center text-4" @click="goToPosition(step.position)">
<KeyReturnIcon/>
</button>
</div>

View file

@ -1,8 +1,5 @@
<template>
<div class="flex flex-col overflow-hidden">
<h1 class="font-800 text-9 px-4 pt-10 pb-0">
{{ current.scene.name }}
</h1>
<div class="flex flex-col space-y-4 p-4 pt-8 flex-grow h-full overflow-hidden">
<div class="flex justify-end">
<button class="px-5 py-3 bg-green-600 font-bold text-5" @click="goNext()">

View file

@ -1,8 +1,5 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-10 pb-0">
{{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-10 pt-8 flex-grow overflow-hidden">
<StepSelection class="w-1/2"/>
<div class="w-1/2 flex flex-col space-y-4">

View file

@ -1,7 +1,7 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.scene.name }}
Szene {{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList center-current class="w-3/7"/>
@ -14,14 +14,8 @@
</div>
</template>
<style scoped>
</style>
<script setup>
<script setup lang="ts">
import MusicProgressBar from "../../components/MusicProgressBar.vue"
import MessageBox from "../../components/MessageEdit.vue"
import ActorsOnStageBox from "../../components/ActorsOnStageBox.vue"
import { current } from "../../state"
import MotionsList from "../../components/MotionsList.vue"
import StageTopDownView from "../../components/StageTopDownView.vue"

View file

@ -1,7 +1,7 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.scene.name }}
Szene {{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList scrollable center-current class="w-3/7"/>

View file

@ -1,40 +0,0 @@
<template>
<div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0">
{{ current.scene.name }}
</h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList center-current class="w-3/7" scrollable/>
<div class="w-4/7 flex flex-col space-y-4">
<StageTopDownView class="h-full"/>
<MessageEdit class="h-30"/>
</div>
</div>
<MusicProgressBar class="h-10"/>
</div>
</template>
<style scoped>
</style>
<script>
import MusicProgressBar from "../components/MusicProgressBar.vue"
import MessageBox from "../components/MessageEdit.vue"
import ActorsOnStageBox from "../components/ActorsOnStageBox.vue"
import { current } from "../state"
import MotionsList from "../components/MotionsList.vue"
import StageTopDownView from "../components/StageTopDownView.vue"
import MessageDisplay from "../components/MessageDisplay.vue"
import MessageEdit from "../components/MessageEdit.vue"
export default {
name: "ReinPage",
components: { MessageEdit, MessageDisplay, StageTopDownView, MotionsList, ActorsOnStageBox, MessageBox, MusicProgressBar },
setup() {
return {
current: current
}
}
}
</script>

View file

@ -9,7 +9,7 @@ export default defineConfig({
splitVendorChunkPlugin(),
vuePlugin(),
pagesPlugin({
syncIndex: false
importMode: "sync"
}),
windicssPlugin(),
iconsPlugin()
@ -21,5 +21,8 @@ export default defineConfig({
ws: true
}
}
},
build: {
reportCompressedSize: false
}
})