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/ /run/
/.idea/ /.idea/
/src/main/resources/ui/*

View file

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

View file

@ -1,8 +1,9 @@
package de.moritzruth.lampenfieber 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.SimpleDimmer
import de.moritzruth.lampenfieber.device.StairvilleTriLedBar import de.moritzruth.lampenfieber.device.StairvilleTriLedBar
import de.moritzruth.lampenfieber.device.Wash
import de.moritzruth.theaterdsl.dmx.DmxAddress import de.moritzruth.theaterdsl.dmx.DmxAddress
import de.moritzruth.theaterdsl.dmx.EnttecOpenDmxUsb import de.moritzruth.theaterdsl.dmx.EnttecOpenDmxUsb
import de.moritzruth.theaterdsl.show.StepCue 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.degrees
import de.moritzruth.theaterdsl.value.percent import de.moritzruth.theaterdsl.value.percent
import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
val bar = StairvilleTriLedBar(DmxAddress(400u))
object FrontLights { object FrontLights {
val left = listOf(SimpleDimmer(DmxAddress(1u)), SimpleDimmer(DmxAddress(2u))) val left = listOf(SimpleDimmer(DmxAddress(1u)), SimpleDimmer(DmxAddress(2u)))
val center = listOf(SimpleDimmer(DmxAddress(4u)), SimpleDimmer(DmxAddress(5u))) val center = listOf(SimpleDimmer(DmxAddress(4u)), SimpleDimmer(DmxAddress(5u)))
@ -27,62 +29,174 @@ object FrontLights {
val spotLeft = SimpleDimmer(DmxAddress(10u)) val spotLeft = SimpleDimmer(DmxAddress(10u))
val spotRight = SimpleDimmer(DmxAddress(11u)) val spotRight = SimpleDimmer(DmxAddress(11u))
// Nebel: SF-1500 object Tops {
val left = FuturelightDmh160(DmxAddress(37u))
object Washs { val right = FuturelightDmh160(DmxAddress(53u))
val left = Wash(DmxAddress(85u)) val both = listOf(left, right)
val right = Wash(DmxAddress(101u))
val all = listOf(left, right)
} }
val devices = persistentSetOf(bar, *FrontLights.all.toTypedArray(), spotLeft, spotRight, *Washs.all.toTypedArray()) object Washs {
val left = CoemarProWash(DmxAddress(85u), false)
val right = CoemarProWash(DmxAddress(101u), true)
val both = listOf(left, right)
}
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 { val show = createShow {
act("Erster Akt") { act("Erster Akt") {
scene("I") { scene("Intro") {
step { step(StepCue.MusicStart("Lampenfieber", 5.minutes + 30.seconds)) {
trigger = StepCue.MusicStart("Lampenfieber", 69.seconds) props {
}
onRun { onRun {
// Lichteffekte in der Aula Washs.both.forEach {
spotRight.brightness.static(100.percent) it.pointAtCeiling()
it.colorWheelMode.static(CoemarProWash.ColorWheelMode.White)
}
}
}
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("I.1") { scene("1.1") {
step { step(StepCue.MusicEnd) {
trigger = 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 { onRun {
spotRight.brightness.static(0.percent) spotRight.brightness.fade(100.percent, 6.seconds)
FrontLights.center.forEach { it.brightness.fade(75.percent, 10.seconds) } FrontLights.center.forEach { it.brightness.fade(75.percent, 10.seconds) }
Washs.both.forEach {
it.pointAtStageCenter()
it.colorWheelMode.static(CoemarProWash.ColorWheelMode.White)
}
} }
} }
step { step(StepCue.MusicStart("Rap", 2.minutes + 30.seconds)) {
trigger = StepCue.MusicStart("Rap", 69.seconds) actors {
// Rapper, Tänzer
}
onRun { onRun {
// sehr viel // Nebel, rot-orange faden
FrontLights.center.forEach { it.brightness.fade(20.percent, 10.seconds) } FrontLights.center.forEach { it.brightness.fade(20.percent, 10.seconds) }
bar.color.static(Color(100.degrees)) bar.brightness.fade(50.percent, 10.seconds)
bar.brightness.fade(75.percent, 10.seconds) bar.color.fadeRandomAround(20.degrees, 10.degrees, 1500.milliseconds)
} }
} }
step { lightStep(StepCue.Custom("T5 S2")) {
trigger = StepCue.MusicEnd Washs.both.forEach {
it.brightness.static(100.percent)
it.beamAngle.sine(0.percent, 100.percent, 3.seconds)
}
}
onRun { lightStep(StepCue.Custom("Schlussschlag")) {
FrontLights.center.forEach { it.brightness.fade(0.percent, 10.seconds) } Washs.both.forEach {
bar.brightness.fade(0.percent, 2.seconds) 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 { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -100,7 +214,7 @@ val show = createShow {
} }
} }
scene("I.3") { scene("1.3") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -111,7 +225,7 @@ val show = createShow {
} }
} }
scene("I.3") { scene("1.3") {
step { step {
trigger = StepCue.Custom("Bühne erreicht") trigger = StepCue.Custom("Bühne erreicht")
@ -124,7 +238,7 @@ val show = createShow {
// David spielt: Licht links // David spielt: Licht links
} }
scene("I.5") { scene("1.5") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -132,7 +246,7 @@ val show = createShow {
FrontLights.all.forEach { it.brightness.fade(0.percent, 10.seconds) } FrontLights.all.forEach { it.brightness.fade(0.percent, 10.seconds) }
FrontLights.right.forEach { it.brightness.fade(50.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) it.brightness.fade(75.percent, 10.seconds)
// Blau // Blau
} }
@ -148,7 +262,7 @@ val show = createShow {
onRun { onRun {
FrontLights.right.forEach { it.brightness.fade(0.percent, 10.seconds) } FrontLights.right.forEach { it.brightness.fade(0.percent, 10.seconds) }
Washs.all.forEach { Washs.both.forEach {
it.brightness.fade(100.percent, 10.seconds) it.brightness.fade(100.percent, 10.seconds)
} }
@ -161,13 +275,13 @@ val show = createShow {
onRun { onRun {
FrontLights.right.forEach { it.brightness.off() } FrontLights.right.forEach { it.brightness.off() }
Washs.all.forEach { it.brightness.off() } Washs.both.forEach { it.brightness.off() }
// Nebel aus // Nebel aus
} }
} }
} }
scene("II.1") { scene("2.1") {
step { step {
trigger = StepCue.MusicStart("Computerspiel", 69.seconds) trigger = StepCue.MusicStart("Computerspiel", 69.seconds)
@ -177,7 +291,7 @@ val show = createShow {
} }
} }
scene("II.2") { scene("2.2") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -188,7 +302,7 @@ val show = createShow {
} }
} }
scene("II.3") { scene("2.3") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -240,7 +354,7 @@ val show = createShow {
} }
} }
scene("III.1") { scene("3.1") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -253,7 +367,7 @@ val show = createShow {
} }
} }
scene("III.2") { scene("3.2") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -272,14 +386,14 @@ val show = createShow {
} }
} }
scene("III.3") { scene("3.3") {
step { step {
trigger = StepCue.MusicStart("Tischballet", 69.seconds) trigger = StepCue.MusicStart("Tischballet", 69.seconds)
// Instrumental, Umbau zu Musiksaal // Instrumental, Umbau zu Musiksaal
} }
} }
scene("III.4") { scene("3.4") {
step { step {
trigger = StepCue.MusicEnd trigger = StepCue.MusicEnd
// Vorhang auf // Vorhang auf
@ -298,7 +412,7 @@ val show = createShow {
} }
} }
scene("III.5") { scene("3.5") {
step { step {
trigger = StepCue.Custom("Ende") trigger = StepCue.Custom("Ende")
@ -311,7 +425,7 @@ val show = createShow {
// Umbau-Musik // Umbau-Musik
} }
scene("III.6") { scene("3.6") {
step { step {
trigger = StepCue.Custom("Vorhang auf, Musik Ende") trigger = StepCue.Custom("Vorhang auf, Musik Ende")
@ -322,7 +436,7 @@ val show = createShow {
} }
} }
scene("III.7") { scene("3.7") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -333,7 +447,7 @@ val show = createShow {
} }
} }
scene("III.8") { scene("3.8") {
step { step {
trigger = StepCue.MusicEnd trigger = StepCue.MusicEnd
@ -372,7 +486,7 @@ val show = createShow {
// RnR entfällt // RnR entfällt
scene("IV.1") { scene("4.1") {
step { step {
trigger = StepCue.MusicStart("Pause", 69.seconds) trigger = StepCue.MusicStart("Pause", 69.seconds)
} }
@ -413,7 +527,7 @@ val show = createShow {
} }
} }
scene("IV.2") { scene("4.2") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -432,7 +546,7 @@ val show = createShow {
} }
} }
scene("IV.3") { scene("4.3") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -443,7 +557,7 @@ val show = createShow {
} }
} }
scene("IV.4") { scene("4.4") {
step { step {
trigger = StepCue.Custom("Auftritt David") trigger = StepCue.Custom("Auftritt David")
@ -475,7 +589,7 @@ val show = createShow {
} }
} }
scene("IV.5") { scene("4.5") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -493,7 +607,7 @@ val show = createShow {
} }
} }
scene("IV.6") { scene("4.6") {
step { step {
trigger = StepCue.MusicEnd trigger = StepCue.MusicEnd
@ -516,7 +630,7 @@ val show = createShow {
} }
} }
scene("IV.7") { scene("4.7") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -545,7 +659,7 @@ val show = createShow {
} }
} }
scene("IV.8") { scene("4.8") {
step { step {
trigger = StepCue.MusicStart("Angstballet", 69.seconds) trigger = StepCue.MusicStart("Angstballet", 69.seconds)
@ -559,7 +673,7 @@ val show = createShow {
} }
} }
scene("IV.9") { scene("4.9") {
step { step {
trigger = StepCue.Stub trigger = StepCue.Stub
@ -577,7 +691,7 @@ val show = createShow {
} }
} }
scene("IV.10") { scene("4.10") {
step { step {
trigger = StepCue.Stub 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 package de.moritzruth.lampenfieber.device
import de.moritzruth.theaterdsl.device.AngleDV import de.moritzruth.theaterdsl.device.AngleDV
import de.moritzruth.theaterdsl.device.ConcreteDV
import de.moritzruth.theaterdsl.device.Device import de.moritzruth.theaterdsl.device.Device
import de.moritzruth.theaterdsl.device.PercentageDV import de.moritzruth.theaterdsl.device.PercentageDV
import de.moritzruth.theaterdsl.dmx.DmxAddress import de.moritzruth.theaterdsl.dmx.DmxAddress
import de.moritzruth.theaterdsl.dmx.DmxDataWriter 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 kotlinx.collections.immutable.persistentSetOf
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class FuturelightDmh160(override val firstChannel: DmxAddress) : Device { class FuturelightDmh160(override val firstChannel: DmxAddress) : Device {
companion object { companion object {
@ -18,22 +23,72 @@ class FuturelightDmh160(override val firstChannel: DmxAddress) : Device {
override val numberOfChannels: UInt = 16u override val numberOfChannels: UInt = 16u
override fun writeDmxData(writer: DmxDataWriter) { override fun writeDmxData(writer: DmxDataWriter) {
val fullPan = (pan.getCurrentValue().degrees.mod(PAN_LIMIT) / PAN_LIMIT * 65536).roundToInt() val pan = (pan.getCurrentValue().degrees.mod(PAN_LIMIT) / PAN_LIMIT * 65536).roundToInt().toUShort()
val (mPan, lPan) = fullPan.get16BitChannelValues() 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() writer.writeHighByte(pan)
val (mTilt, lTilt) = fullTilt.get16BitChannelValues() writer.writeLowByte(pan)
writer.writeHighByte(tilt)
writer.writeRaw(mPan) writer.writeLowByte(tilt)
writer.writeRaw(lPan) writer.writeRaw(DmxValue(0u)) // pan/tilt speed
writer.writeRaw(mTilt) writer.writeRaw(DmxValue(0u)) // blackout while moving = off
writer.writeRaw(lTilt) writer.writeRaw(DmxValue((strobeSpeed.getCurrentValue().value * 32 + 63).roundToInt().toUByte()))
writer.writePercentage(brightness.getCurrentValue()) 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 pan = AngleDV()
val tilt = AngleDV() val tilt = AngleDV()
val brightness = PercentageDV() 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.asin
import kotlin.math.min import kotlin.math.min
import kotlin.math.sin import kotlin.math.sin
import kotlin.random.Random
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource 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 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 toDomain(value: Float): T
protected abstract fun fromDomain(value: T): Float 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 stateChangeMark = TimeSource.Monotonic.markNow()
private var state: State = State.Static(initialStaticValue) 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 val index = (elapsedTime / s.interval).toInt() + s.startIndex
s.steps[index.mod(s.steps.size)] 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) return toDomain(float)
@ -107,16 +123,44 @@ abstract class FloatDV<T>(private val initialStaticValue: Float) : DynamicValue<
steps(persistentListOf(a, b), interval, startIndex) 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) { class PercentageDV(initialStaticValue: Percentage = 0.percent) : FloatDV<Percentage>(initialStaticValue.value) {
override fun fromDomain(value: Percentage): Float = value.value override fun fromDomain(value: Percentage): Float = value.value
override fun toDomain(value: Float): Percentage = Percentage(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) { class AngleDV(initialStaticValue: Angle = 0.degrees) : FloatDV<Angle>(initialStaticValue.degrees) {
override fun fromDomain(value: Angle): Float = value.degrees override fun fromDomain(value: Angle): Float = value.degrees
override fun toDomain(value: Float): Angle = Angle(value) 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) @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 deltaSaturation = end.saturation.value - start.saturation.value
val deltaBrightness = end.brightness.value - start.brightness.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() 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()) 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()) { fun fade(end: Color, duration: Duration, start: Color = getCurrentValue()) {
state = State.Fade(start, end, duration) 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) = fun writeInRange(range: ClosedFloatingPointRange<Float>, value: Float, startAtOne: Boolean = false) =
writeRaw(Percentage((value - range.start) / (range.endInclusive - range.start)).roundToDmxValue(startAtOne)) 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

@ -17,9 +17,4 @@ value class DmxValue(val value: UByte) : Comparable<UByte> {
*/ */
fun Percentage.roundToDmxValue(startAtOne: Boolean = false): DmxValue = fun Percentage.roundToDmxValue(startAtOne: Boolean = false): DmxValue =
if (startAtOne) DmxValue(((value * (DmxValue.VALUE_RANGE.last.toFloat() - 1f)).roundToInt() + 1).toUByte()) if (startAtOne) DmxValue(((value * (DmxValue.VALUE_RANGE.last.toFloat() - 1f)).roundToInt() + 1).toUByte())
else DmxValue((value * DmxValue.VALUE_RANGE.last.toFloat()).roundToInt().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) Thread.sleep(23)
} }
} catch (exception: SerialPortIOException) { } catch (exception: SerialPortIOException) {
exception.printStackTrace() // only thrown when the program is stopped
port.closePort()
} }
} }

View file

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

View file

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

View file

@ -5,8 +5,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"dev": "vite --port 3000 --host", "dev": "vite --port 3000 --host",
"build": "vite build", "build": "vite build --emptyOutDir --outDir ../src/main/resources/ui"
"start": "vite preview --port 3000 --host"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/ph": "^1.1.5", "@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> <template>
<div class="flex items-start space-x-2"> <div class="flex items-start space-x-2" :class="textClass ?? 'text-4'">
<component :is="icon" class="text-4 mt-0.5 flex-shrink-0"/> <component :is="icon" class="mt-0.5 flex-shrink-0"/>
<div class="text-4" :class="singleLine && 'truncate'"> <div :class="singleLine && 'truncate'">
{{ text }} {{ text }}
</div> </div>
</div> </div>
@ -24,6 +24,7 @@
const props = defineProps<{ const props = defineProps<{
step: Step, step: Step,
singleLine?: boolean singleLine?: boolean
textClass?: string
}>() }>()
const icon = computed(() => { const icon = computed(() => {

View file

@ -4,8 +4,8 @@
v-for="(scene, sceneIndex) in scenes" v-for="(scene, sceneIndex) in scenes"
:key="sceneIndex" :key="sceneIndex"
> >
<div class="text-gray-400 pl-3"> <div class="text-gray-400 pl-3 text-5">
{{ scene.name }} Szene {{ scene.name }}
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<template v-for="step in scene.steps" :key="step.position"> <template v-for="step in scene.steps" :key="step.position">
@ -22,7 +22,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { show } from "../state" import { Scene, show, ShowPosition, Step } from "../state"
import { computed } from "vue" import { computed } from "vue"
import MotionsListStep from "./MotionsListStep.vue" import MotionsListStep from "./MotionsListStep.vue"
@ -31,5 +31,41 @@
scrollable?: boolean 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> </script>

View file

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

View file

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

View file

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

View file

@ -1,8 +1,5 @@
<template> <template>
<div class="flex flex-col overflow-hidden"> <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 flex-col space-y-4 p-4 pt-8 flex-grow h-full overflow-hidden">
<div class="flex justify-end"> <div class="flex justify-end">
<button class="px-5 py-3 bg-green-600 font-bold text-5" @click="goNext()"> <button class="px-5 py-3 bg-green-600 font-bold text-5" @click="goNext()">

View file

@ -1,8 +1,5 @@
<template> <template>
<div class="flex flex-col h-full"> <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"> <div class="h-full flex space-x-4 p-10 pt-8 flex-grow overflow-hidden">
<StepSelection class="w-1/2"/> <StepSelection class="w-1/2"/>
<div class="w-1/2 flex flex-col space-y-4"> <div class="w-1/2 flex flex-col space-y-4">

View file

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

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<h1 class="font-800 text-9 p-4 pb-0"> <h1 class="font-800 text-9 p-4 pb-0">
{{ current.scene.name }} Szene {{ current.scene.name }}
</h1> </h1>
<div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden"> <div class="h-full flex space-x-4 p-4 pt-8 flex-grow overflow-hidden">
<MotionsList scrollable center-current class="w-3/7"/> <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(), splitVendorChunkPlugin(),
vuePlugin(), vuePlugin(),
pagesPlugin({ pagesPlugin({
syncIndex: false importMode: "sync"
}), }),
windicssPlugin(), windicssPlugin(),
iconsPlugin() iconsPlugin()
@ -21,5 +21,8 @@ export default defineConfig({
ws: true ws: true
} }
} }
},
build: {
reportCompressedSize: false
} }
}) })