From c3a0bea5ebd83d8b2374fdfba19911467a534cfe Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Sat, 5 Apr 2025 00:14:14 +0200 Subject: [PATCH] commit 100 --- .../de/moritzruth/dracula_musical/Main.kt | 2 + .../de/moritzruth/dracula_musical/act/Act2.kt | 12 +- .../moritzruth/dracula_musical/act/Testing.kt | 47 +++++++ .../dracula_musical/device/Devices.kt | 10 +- .../dracula_musical/device/StairvilleSplb.kt | 4 +- .../device/StairvilleTlbButItsSplb.kt | 117 +++++++++++++----- .../dracula_musical/song/AdelIstGeil.kt | 2 +- .../dracula_musical/song/DraculasZorn.kt | 20 +-- .../dracula_musical/song/DuettDraculaMina.kt | 2 +- .../dracula_musical/song/DuettMinaJonathan.kt | 2 +- .../dracula_musical/song/EsIstAngerichtet.kt | 2 +- .../moritzruth/dracula_musical/song/Finale.kt | 2 +- .../dracula_musical/song/FinaleErsterAkt.kt | 58 ++++----- .../dracula_musical/song/Irrenhaus.kt | 8 +- .../dracula_musical/song/Kaffeeklatsch.kt | 10 +- .../moritzruth/dracula_musical/song/Lucy.kt | 10 +- .../dracula_musical/song/Maskenball.kt | 4 +- .../dracula_musical/song/Mittsommernacht.kt | 14 +-- .../dracula_musical/song/RepriseDuett.kt | 2 +- .../dracula_musical/song/RepriseMaskenball.kt | 4 +- .../dracula_musical/song/StreitDerVampire.kt | 2 +- .../moritzruth/theaterdsl/device/EffectDV.kt | 38 ++++++ .../de/moritzruth/theaterdsl/value/Color.kt | 4 + .../moritzruth/theaterdsl/value/Percentage.kt | 2 + 24 files changed, 267 insertions(+), 111 deletions(-) create mode 100644 src/main/kotlin/de/moritzruth/dracula_musical/act/Testing.kt create mode 100644 src/main/kotlin/de/moritzruth/theaterdsl/device/EffectDV.kt diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/Main.kt b/src/main/kotlin/de/moritzruth/dracula_musical/Main.kt index 35c5c12..d1045ed 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/Main.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/Main.kt @@ -2,6 +2,7 @@ package de.moritzruth.dracula_musical import de.moritzruth.dracula_musical.act.act1 import de.moritzruth.dracula_musical.act.act2 +import de.moritzruth.dracula_musical.act.testingAct import de.moritzruth.dracula_musical.device.devices import de.moritzruth.dracula_musical.device.fogMachine import de.moritzruth.theaterdsl.dmx.EnttecOpenDmxUsb @@ -9,6 +10,7 @@ import de.moritzruth.theaterdsl.show.createShow import de.moritzruth.theaterdsl.show.runShow val show = createShow { + testingAct() act1() act2() } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/act/Act2.kt b/src/main/kotlin/de/moritzruth/dracula_musical/act/Act2.kt index 053071c..96969de 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/act/Act2.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/act/Act2.kt @@ -116,7 +116,7 @@ fun ShowBuilderContext.act2() = act("2. Akt") { BlinderBars.all { it.brightness.fade(100.percent, 2.seconds) it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(100.percent) + it.effect.speed.static(100.percent) } Tops.both { @@ -307,7 +307,7 @@ fun ShowBuilderContext.act2() = act("2. Akt") { BlinderBars.all { it.brightness.fade(100.percent, 2.seconds) it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(100.percent) + it.effect.speed.static(100.percent) } } @@ -349,7 +349,7 @@ fun ShowBuilderContext.act2() = act("2. Akt") { BlinderBars.all { it.brightness.fade(100.percent, 2.seconds) it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(100.percent) + it.effect.speed.static(100.percent) } Tops.both { @@ -391,7 +391,7 @@ fun ShowBuilderContext.act2() = act("2. Akt") { lightStep(StepCue.Custom("Musik: Einsetzen der Drums")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) it.brightness.fade(40.percent, 0.5.seconds) } } @@ -488,14 +488,14 @@ fun ShowBuilderContext.act2() = act("2. Akt") { BlinderBars.all { it.brightness.fade(50.percent, 2.seconds) it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(100.percent) + it.effect.speed.static(100.percent) } } lightStep(StepCue.Custom("Hawkins entreißt Van Helsing das Kreuz")) { BlinderBars.all { it.brightness.off(2.seconds) - it.presetSpeed.off(0.5.seconds) + it.effect.speed.off(0.5.seconds) } Washs.both { diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/act/Testing.kt b/src/main/kotlin/de/moritzruth/dracula_musical/act/Testing.kt new file mode 100644 index 0000000..d667621 --- /dev/null +++ b/src/main/kotlin/de/moritzruth/dracula_musical/act/Testing.kt @@ -0,0 +1,47 @@ +package de.moritzruth.dracula_musical.act + +import de.moritzruth.dracula_musical.device.BlinderBars +import de.moritzruth.dracula_musical.device.StairvilleSplb +import de.moritzruth.theaterdsl.show.ShowBuilderContext +import de.moritzruth.theaterdsl.show.StepCue +import de.moritzruth.theaterdsl.value.percent + +fun ShowBuilderContext.testingAct() = act("Testing") { + scene("Testing") { + lightStep(StepCue.Custom("null")) { + BlinderBars.all { + it.brightness.static(100.percent) + it.effect.static(null) + it.white.static(100.percent) + } + } + + lightStep(StepCue.Custom("StairvilleSplb.Effect.FLOW_INWARDS")) { + BlinderBars.all { + it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) + it.effect.speed.static(50.percent) + } + } + + lightStep(StepCue.Custom("StairvilleSplb.Effect.FLOW_INWARDS_LONG")) { + BlinderBars.all { + it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS_LONG) + it.effect.speed.static(50.percent) + } + } + + lightStep(StepCue.Custom("StairvilleSplb.Effect.THEATRE_SWITCHING")) { + BlinderBars.all { + it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) + it.effect.speed.static(20.percent) + } + } + + lightStep(StepCue.Custom("StairvilleSplb.Effect.HECTIC_SWITCHING")) { + BlinderBars.all { + it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) + it.effect.speed.static(100.percent) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/device/Devices.kt b/src/main/kotlin/de/moritzruth/dracula_musical/device/Devices.kt index 70d3956..cb3321a 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/device/Devices.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/device/Devices.kt @@ -36,13 +36,15 @@ object Washs { object BlinderBars { //val inner = DeviceGroup(StairvilleSplb(DmxAddress(155u)), StairvilleSplb(DmxAddress(178u))) //val outer = DeviceGroup(StairvilleSplb(DmxAddress(201u)), StairvilleSplb(DmxAddress(224u))) - val inner = DeviceGroup(StairvilleTlbButItsSplb(DmxAddress(155u)), StairvilleTlbButItsSplb(DmxAddress(178u))) - val outer = DeviceGroup(StairvilleTlbButItsSplb(DmxAddress(201u)), StairvilleTlbButItsSplb(DmxAddress(224u))) + val inner = DeviceGroup(StairvilleTlbButItsSplb(DmxAddress(130u)), StairvilleTlbButItsSplb(DmxAddress(470u))) + val outer = DeviceGroup(StairvilleTlbButItsSplb(DmxAddress(470u)), StairvilleTlbButItsSplb(DmxAddress(470u))) val all = inner + outer } -val backlightBar = StairvilleTlb(DmxAddress(130u)) -val sidelight = StairvilleClb4(DmxAddress(140u)) +//val backlightBar = StairvilleTlb(DmxAddress(130u)) +val backlightBar = StairvilleTlb(DmxAddress(470u)) +//val sidelight = StairvilleClb4(DmxAddress(140u)) +val sidelight = StairvilleClb4(DmxAddress(470u)) val fogMachine = AdjFogFury3000(DmxAddress(117u)) diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleSplb.kt b/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleSplb.kt index d00b311..a8231b8 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleSplb.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleSplb.kt @@ -31,7 +31,9 @@ class StairvilleSplb(override val firstChannel: DmxAddress) : Device { VOLATILE_SPARKLES, BUMP_INWARDS, FLOW_INWARDS, - FLOW_OUTWARDS; + FLOW_INWARDS_LONG, + FLOW_OUTWARDS, + FLOW_OUTWARDS_LONG; } override val dvs: ImmutableSet> = persistentSetOf( diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleTlbButItsSplb.kt b/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleTlbButItsSplb.kt index af615a7..e181c52 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleTlbButItsSplb.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/device/StairvilleTlbButItsSplb.kt @@ -4,14 +4,22 @@ import de.moritzruth.dracula_musical.device.StairvilleSplb.Effect import de.moritzruth.theaterdsl.device.* import de.moritzruth.theaterdsl.dmx.DmxAddress import de.moritzruth.theaterdsl.dmx.DmxDataWriter -import de.moritzruth.theaterdsl.dmx.DmxValue import de.moritzruth.theaterdsl.value.Color +import de.moritzruth.theaterdsl.value.Percentage +import de.moritzruth.theaterdsl.value.degrees import de.moritzruth.theaterdsl.value.percent import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf -import kotlin.math.roundToInt +import kotlin.math.PI +import kotlin.math.min +import kotlin.math.roundToLong +import kotlin.math.sin + +class StairvilleTlbButItsSplb(override val firstChannel: DmxAddress, val inwardsIsRight: Boolean = false) : Device { + companion object { + private const val NUMBER_OF_SEGMENTS = 9 + } -class StairvilleTlbButItsSplb(override val firstChannel: DmxAddress) : Device { override val numberOfChannels: UInt = 27u val brightness = PercentageDV() @@ -20,8 +28,74 @@ class StairvilleTlbButItsSplb(override val firstChannel: DmxAddress) : Device { val color = ColorDV(Color.WHITE.copy(brightness = 0.percent)) val white = PercentageDV() - val effect = ConcreteDV(null) - val presetSpeed = PercentageDV() + val effect = object : EffectDV>(null) { + override fun compute(time: Long, effect: Effect?): Array { + val white = white.getCurrentValue() + val color = if (white == 0.percent) color.getCurrentValue() else Color(30.degrees, 85.percent, white) + + fun fadeInOut( + time: Long, + rampUpDuration: Long, + sustainDuration: Long, + rampDownDuration: Long, + peak: Percentage = 100.percent, + start: Percentage = 0.percent, + end: Percentage = 0.percent + ): Percentage { + return if (time <= rampUpDuration) { + val progress = time / rampUpDuration.toDouble() + val delta = peak.value - start.value + Percentage(progress * delta + start.value) + } else if (time <= rampUpDuration + sustainDuration) { + peak + } else { + val progress = min((time - rampUpDuration - sustainDuration) / rampDownDuration.toDouble(), 1.0) + val delta = peak.value - end.value + Percentage(peak.value - progress * delta) + } + } + + fun reverseIndexIfInwardsIsRight(index: Int) = if (inwardsIsRight) NUMBER_OF_SEGMENTS - index else index + + return when (effect) { + null -> Array(NUMBER_OF_SEGMENTS) { color } + Effect.HECTIC_SWITCHING -> { + val step = (time / 100).mod(2) + Array(NUMBER_OF_SEGMENTS) { index -> if ((index + step) % 2 == 0) color else Color.BLACK } + } + + Effect.THEATRE_SWITCHING -> { + val interval = 500L + Array(NUMBER_OF_SEGMENTS) { index -> + color.multiplyBrightness( + fadeInOut( + (time + (if (index % 2 == 0) interval / 2 else 0)).mod(interval), + rampUpDuration = (interval * (1 / 8.0)).roundToLong(), + sustainDuration = (interval * (1 / 4.0)).roundToLong(), + rampDownDuration = (interval * (1 / 4.0)).roundToLong(), + ) + ) + } + } + + Effect.FLOW_INWARDS, Effect.FLOW_INWARDS_LONG -> { + val interval = 1000.0 + val scale = if (effect == Effect.FLOW_INWARDS) 0.5 else 1.5 + + Array(NUMBER_OF_SEGMENTS) { index -> + val value = Percentage( + sin((time + reverseIndexIfInwardsIsRight(index) * (interval / NUMBER_OF_SEGMENTS)) / interval * 2 * PI / scale) + / 2 + 0.5 + ) + + color.multiplyBrightness(value) + } + } + + else -> TODO() + } + } + } override val dvs: ImmutableSet> = persistentSetOf( brightness, @@ -29,34 +103,17 @@ class StairvilleTlbButItsSplb(override val firstChannel: DmxAddress) : Device { color, white, effect, - presetSpeed ) override fun writeDmxData(writer: DmxDataWriter, isLightBehindCurtainOn: Boolean) { - writer.writePercentage(brightness.getCurrentValue()) + val brightness = brightness.getCurrentValue() + val segments = effect.getCurrentValue() - val strobeFrequency = strobeFrequency.getCurrentValue() - if (strobeFrequency == 0.percent) writer.writeRaw(DmxValue(0u)) - else writer.writeRaw(DmxValue(strobeFrequency.ofRange(11.0..255.0).roundToInt().toUByte())) - - // mode - val preset = effect.getCurrentValue() - if (preset == null) writer.writeRaw(DmxValue(0u)) // mode = RGBW - else writer.writeRaw(DmxValue(128u)) // mode = preset - - writer.writeRaw(DmxValue(0u)) // color preset (not used) - - if (preset == null) writer.writeRaw(DmxValue(0u)) - else writer.writeRaw(preset.dmxValue) - - writer.writeRaw(DmxValue(0u)) // sound mode preset - - writer.writePercentage(presetSpeed.getCurrentValue()) - - val (red, green, blue) = color.getCurrentValue().getRGB() - writer.writePercentage(red) - writer.writePercentage(green) - writer.writePercentage(blue) - writer.writePercentage(white.getCurrentValue()) + for (segment in segments) { + val (r, g, b) = segment.multiplyBrightness(brightness).powBrightness(2.5).getRGB() + writer.writePercentage(r) + writer.writePercentage(g) + writer.writePercentage(b) + } } } \ No newline at end of file diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/AdelIstGeil.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/AdelIstGeil.kt index ed4d750..01fc813 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/AdelIstGeil.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/AdelIstGeil.kt @@ -13,7 +13,7 @@ fun SceneBuilderContext.songAdelIstGeil() { BlinderBars.all { it.brightness.fade(25.percent, 5.seconds) it.effect.static(StairvilleSplb.Effect.LAZY_SPARKLES) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/DraculasZorn.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/DraculasZorn.kt index 440bba5..d000b11 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/DraculasZorn.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/DraculasZorn.kt @@ -28,7 +28,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(75.percent) + it.effect.speed.static(75.percent) it.brightness.ramp(1.seconds, 25.percent, 0.percent) } @@ -48,7 +48,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.brightness.fade(10.percent, 1.seconds) - it.presetSpeed.static(10.percent) + it.effect.speed.static(10.percent) it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) } } @@ -67,7 +67,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.brightness.fade(25.percent, 1.5.seconds) it.effect.static(StairvilleSplb.Effect.LAZY_SPARKLES) - it.presetSpeed.fade(25.percent, 1.5.seconds) + it.effect.speed.fade(25.percent, 1.5.seconds) } } @@ -82,7 +82,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) // red, chase, back-and-forth - it.presetSpeed.fade(75.percent, 1.seconds) + it.effect.speed.fade(75.percent, 1.seconds) } } @@ -94,7 +94,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.brightness.fade(30.percent, 8.seconds) - it.presetSpeed.fade(25.percent, 1.5.seconds) + it.effect.speed.fade(25.percent, 1.5.seconds) it.effect.static(StairvilleSplb.Effect.LAZY_SPARKLES) // blink-y, red } } @@ -146,7 +146,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(25.percent) + it.effect.speed.static(25.percent) it.brightness.fade(25.percent, 2.seconds) } @@ -194,7 +194,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.brightness.fade(25.percent, 1.seconds) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.effect.static(StairvilleSplb.Effect.LAZY_SPARKLES) } } @@ -211,7 +211,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.brightness.off(3.seconds) - it.presetSpeed.fade(0.percent, 1.seconds) + it.effect.speed.fade(0.percent, 1.seconds) } } @@ -247,7 +247,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(90.percent) + it.effect.speed.static(90.percent) it.color.reset() it.brightness.fade(30.percent, 0.5.seconds) } @@ -308,7 +308,7 @@ fun SceneBuilderContext.songDraculasZorn() { BlinderBars.all { it.brightness.fade(30.percent, 8.seconds) - it.presetSpeed.static(100.percent) + it.effect.speed.static(100.percent) } Washs.both { it.brightness.ramp(0.6.seconds, 100.percent, 0.percent) } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettDraculaMina.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettDraculaMina.kt index 102745a..4b8a748 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettDraculaMina.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettDraculaMina.kt @@ -33,7 +33,7 @@ fun SceneBuilderContext.songDuettDraculaMina() { lightStep(StepCue.Text("Mina", "Ich kenne dich!", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(10.percent) + it.effect.speed.static(10.percent) it.brightness.fade(25.percent, 2.seconds) } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettMinaJonathan.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettMinaJonathan.kt index 630237c..9610cad 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettMinaJonathan.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/DuettMinaJonathan.kt @@ -21,7 +21,7 @@ fun SceneBuilderContext.songDuettMinaJonathan() { lightStep(StepCue.Text("Mina", "Ich hab seit gestern…", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) // dreamy, flowy, pink, warm - it.presetSpeed.static(10.percent) + it.effect.speed.static(10.percent) it.brightness.fade(25.percent, 5.seconds) } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/EsIstAngerichtet.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/EsIstAngerichtet.kt index 0e2f2af..9c00c82 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/EsIstAngerichtet.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/EsIstAngerichtet.kt @@ -35,7 +35,7 @@ fun SceneBuilderContext.songEsIstAngerichtet() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(10.percent, 10.seconds) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/Finale.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/Finale.kt index b9e2d73..bcc07af 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/Finale.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/Finale.kt @@ -25,7 +25,7 @@ fun SceneBuilderContext.songFinale() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.LAZY_SPARKLES) - it.presetSpeed.fade(start = 0.percent, end = 50.percent, duration = 20.seconds) + it.effect.speed.fade(start = 0.percent, end = 50.percent, duration = 20.seconds) it.brightness.fade(25.percent, 3.seconds) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/FinaleErsterAkt.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/FinaleErsterAkt.kt index bc94aae..f4d01eb 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/FinaleErsterAkt.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/FinaleErsterAkt.kt @@ -45,7 +45,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.VOLATILE_SPARKLES) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.pulseOnce(0.1.seconds, 0.5.seconds, end = 25.percent) } } @@ -56,7 +56,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.pulseOnce(0.1.seconds, 0.2.seconds, end = 25.percent) } } @@ -67,7 +67,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -85,7 +85,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Text("Dracula", "Ich will diese Frau!", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(25.percent, 0.4.seconds) } } @@ -96,7 +96,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -123,7 +123,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(25.percent, 0.4.seconds) } } @@ -142,7 +142,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Text("Dracula", "Ich sorg für euch!", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(25.percent, 0.4.seconds) } } @@ -153,7 +153,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -171,7 +171,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Text("Dracula", "Ihr müsst mir vertrau’n…", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(25.percent, 0.4.seconds) } } @@ -184,7 +184,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -207,7 +207,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Text("Bernadette", "Das Essen ist weg…", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.VOLATILE_SPARKLES) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(25.percent, 0.4.seconds) } } @@ -226,7 +226,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Text("Dracula", "Ich brauche ihn noch!", "Anfang")) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.brightness.fade(25.percent, 0.4.seconds) } } @@ -237,7 +237,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -247,7 +247,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -257,7 +257,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -267,7 +267,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -279,7 +279,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -289,7 +289,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -299,7 +299,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -314,7 +314,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(70.percent) + it.effect.speed.static(70.percent) } } @@ -391,7 +391,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.color.reset() it.effect.static(StairvilleSplb.Effect.FLOW_INWARDS) - it.presetSpeed.static(15.percent) + it.effect.speed.static(15.percent) it.brightness.fade(30.percent, 2.seconds) } } @@ -417,7 +417,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.brightness.pulseOnce(0.4.seconds, 0.4.seconds, peak = 60.percent, end = 30.percent) it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) } } @@ -443,7 +443,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.brightness.fade(25.percent, 1.seconds) it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) } } @@ -454,7 +454,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Text("Vampire", "Komm zu uns morgen nacht!", "Anfang")) { FrontLights.center { it.brightness.fade(100.percent, 5.seconds) } - BlinderBars.all { it.presetSpeed.fade(1.percent, 3.seconds) } + BlinderBars.all { it.effect.speed.fade(1.percent, 3.seconds) } sidelight.colors.forEach { it.fade(Color.WARM_WHITE, 8.seconds) } } @@ -468,7 +468,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.brightness.fade(30.percent, 1.seconds) it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) } } @@ -479,7 +479,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { lightStep(StepCue.Custom("Musik: Schlag nach »Morgen!«")) { BlinderBars.all { it.brightness.pulseOnce(0.1.seconds, 0.5.seconds, end = 30.percent) - it.presetSpeed.fade(0.percent, 0.4.seconds) + it.effect.speed.fade(0.percent, 0.4.seconds) } } @@ -489,7 +489,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { } lightStep(StepCue.Text("Dracula", "Morgen nacht!", "letzte Silbe")) { - BlinderBars.all { it.presetSpeed.fade(40.percent, 1.seconds) } + BlinderBars.all { it.effect.speed.fade(40.percent, 1.seconds) } } lightStep(StepCue.Custom("Musik: 1. Beat nach »Morgen!«")) { @@ -507,7 +507,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.BUMP_INWARDS) - it.presetSpeed.static(60.percent) + it.effect.speed.static(60.percent) it.brightness.fade(40.percent, 3.seconds) } } @@ -529,7 +529,7 @@ fun SceneBuilderContext.songFinaleErsterAkt() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) - it.presetSpeed.static(80.percent) + it.effect.speed.static(80.percent) it.brightness.fade(60.percent, 5.seconds) } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/Irrenhaus.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/Irrenhaus.kt index 3b70c14..199b542 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/Irrenhaus.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/Irrenhaus.kt @@ -95,7 +95,7 @@ fun SceneBuilderContext.songIrrenhaus() { backlightBar.brightness.fade(100.percent, 3.seconds) BlinderBars.all { - it.presetSpeed.static(80.percent) + it.effect.speed.static(80.percent) it.brightness.off(3.seconds) } } @@ -122,7 +122,7 @@ fun SceneBuilderContext.songIrrenhaus() { lightStep(StepCue.Text("Alle", "Wir sind bescheuert!", "Ende")) { BlinderBars.all { - it.effect.static(StairvilleSplb.Effect.VOLATILE_SPARKLES) // jumpy, chaotic + it.effect.static(StairvilleSplb.Effect.VOLATILE_SPARKLES) it.brightness.fade(40.percent, 1.seconds) } @@ -151,7 +151,7 @@ fun SceneBuilderContext.songIrrenhaus() { backlightBar.brightness.fade(100.percent, 3.seconds) BlinderBars.all { - it.presetSpeed.static(80.percent) + it.effect.speed.static(80.percent) it.brightness.off(3.seconds) } } @@ -190,7 +190,7 @@ fun SceneBuilderContext.songIrrenhaus() { } lightStep(StepCue.Text("Alle", "Gott sei Dank!", "Anfang")) { - BlinderBars.all { it.presetSpeed.fade(80.percent, 4.seconds) } + BlinderBars.all { it.effect.speed.fade(80.percent, 4.seconds) } Washs.both { it.brightness.off(0.5.seconds) diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/Kaffeeklatsch.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/Kaffeeklatsch.kt index 3bc39e2..717e6c8 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/Kaffeeklatsch.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/Kaffeeklatsch.kt @@ -10,10 +10,12 @@ import kotlin.time.Duration.Companion.seconds fun SceneBuilderContext.songKaffeeklatsch() { step(StepCue.MusicStart("Kaffeeklatsch", 2.minutes + 35.seconds)) { - BlinderBars.all { - it.effect.static(StairvilleSplb.Effect.VOLATILE_SPARKLES) - it.presetSpeed.static(30.percent) - it.brightness.fade(25.percent, 4.seconds) + onRun { + BlinderBars.all { + it.effect.static(StairvilleSplb.Effect.VOLATILE_SPARKLES) + it.effect.speed.static(30.percent) + it.brightness.fade(25.percent, 4.seconds) + } } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/Lucy.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/Lucy.kt index 6888737..7de1103 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/Lucy.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/Lucy.kt @@ -51,7 +51,7 @@ fun SceneBuilderContext.songLucy() { BlinderBars.all { it.brightness.fade(25.percent, 2.seconds) it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) // warm/pink - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) } } @@ -84,7 +84,7 @@ fun SceneBuilderContext.songLucy() { lightStep(StepCue.Custom("Musik: Langes Fill-in beginnt")) { BlinderBars.all { - it.presetSpeed.fade(100.percent, 5.seconds) + it.effect.speed.fade(100.percent, 5.seconds) it.brightness.fade(50.percent, 5.seconds) } } @@ -92,7 +92,7 @@ fun SceneBuilderContext.songLucy() { lightStep(StepCue.Custom("Musik: Fill-in endet")) { BlinderBars.all { it.brightness.pulseOnce(0.1.seconds, 1.5.seconds, end = 25.percent) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) } FrontLights.center { it.brightness.fade(100.percent, 1.seconds) } @@ -110,7 +110,7 @@ fun SceneBuilderContext.songLucy() { BlinderBars.all { it.brightness.fade(10.percent, 0.4.seconds) - it.presetSpeed.static(60.percent) + it.effect.speed.static(60.percent) } } @@ -129,7 +129,7 @@ fun SceneBuilderContext.songLucy() { lightStep(StepCue.Text("Lucy", "Mein Sexidol", "vierte Silbe")) { BlinderBars.all { it.brightness.pulseOnce(1.seconds, 2.5.seconds, end = 25.percent) - it.presetSpeed.fade(25.percent, 1.seconds) + it.effect.speed.fade(25.percent, 1.seconds) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/Maskenball.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/Maskenball.kt index 92441fa..a083dca 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/Maskenball.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/Maskenball.kt @@ -65,14 +65,14 @@ fun SceneBuilderContext.songMaskenball() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) // warm - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) it.brightness.fade(25.percent, 2.seconds) } } lightStep(StepCue.Custom("Musik: Breakdown am Ende")) { backlightBar.color.cycle(20.seconds) - BlinderBars.all { it.presetSpeed.fade(0.percent, 5.seconds) } + BlinderBars.all { it.effect.speed.fade(0.percent, 5.seconds) } Washs.both { it.brightness.off(5.seconds) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/Mittsommernacht.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/Mittsommernacht.kt index 2c5d89e..4cde485 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/Mittsommernacht.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/Mittsommernacht.kt @@ -44,14 +44,14 @@ fun SceneBuilderContext.songMittsommernacht() { BlinderBars.all { it.brightness.fade(25.percent, 1.seconds) it.white.static(0.percent) - it.presetSpeed.static(20.percent) + it.effect.speed.static(20.percent) it.effect.static(StairvilleSplb.Effect.FLOW_OUTWARDS) } } lightStep(StepCue.Text("Alle", "Heute sind die Gespenster los…", "Anfang")) { BlinderBars.all { - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.effect.static(StairvilleSplb.Effect.LAZY_SPARKLES) } } @@ -72,7 +72,7 @@ fun SceneBuilderContext.songMittsommernacht() { BlinderBars.all { it.brightness.off(1.seconds) - it.presetSpeed.static(5.percent) + it.effect.speed.static(5.percent) it.effect.static(StairvilleSplb.Effect.CHASE_BACK_AND_FORTH) } } @@ -108,7 +108,7 @@ fun SceneBuilderContext.songMittsommernacht() { BlinderBars.all { it.brightness.fade(25.percent, 1.seconds) - it.presetSpeed.static(50.percent) + it.effect.speed.static(50.percent) it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) } } @@ -181,7 +181,7 @@ fun SceneBuilderContext.songMittsommernacht() { it.color.reset() it.brightness.fade(25.percent, 1.seconds) it.effect.static(StairvilleSplb.Effect.CHASE_INWARDS) - it.presetSpeed.static(80.percent) + it.effect.speed.static(80.percent) } } } @@ -195,7 +195,7 @@ fun SceneBuilderContext.songMittsommernacht() { Washs.both { it.brightness.off(5.seconds) } BlinderBars.all { - it.presetSpeed.fade(5.percent, 2.seconds) + it.effect.speed.fade(5.percent, 2.seconds) it.brightness.fade(60.percent, 10.seconds) } } @@ -244,7 +244,7 @@ fun SceneBuilderContext.songMittsommernacht() { BlinderBars.all { it.brightness.fade(30.percent, 3.seconds) - it.presetSpeed.static(10.percent) + it.effect.speed.static(10.percent) it.effect.static(StairvilleSplb.Effect.HECTIC_SWITCHING) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseDuett.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseDuett.kt index b43e249..bea1240 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseDuett.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseDuett.kt @@ -21,7 +21,7 @@ fun SceneBuilderContext.songRepriseDuett() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.FLOW_OUTWARDS) // pink/warm - it.presetSpeed.static(10.percent) + it.effect.speed.static(10.percent) it.brightness.fade(25.percent, 5.seconds) } } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseMaskenball.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseMaskenball.kt index 6184495..b522ad6 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseMaskenball.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/RepriseMaskenball.kt @@ -39,7 +39,7 @@ fun SceneBuilderContext.songRepriseMaskenball() { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.THEATRE_SWITCHING) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) it.brightness.fade(25.percent, 2.seconds) } } @@ -60,7 +60,7 @@ fun SceneBuilderContext.songRepriseMaskenball() { backlightBar.brightness.pulseOnce(2.seconds, 6.seconds) BlinderBars.all { - it.presetSpeed.fade(0.percent, 2.seconds) + it.effect.speed.fade(0.percent, 2.seconds) it.brightness.pulseOnce(1.seconds, 3.seconds, peak = 50.percent) } diff --git a/src/main/kotlin/de/moritzruth/dracula_musical/song/StreitDerVampire.kt b/src/main/kotlin/de/moritzruth/dracula_musical/song/StreitDerVampire.kt index 4f8111c..a4698fc 100644 --- a/src/main/kotlin/de/moritzruth/dracula_musical/song/StreitDerVampire.kt +++ b/src/main/kotlin/de/moritzruth/dracula_musical/song/StreitDerVampire.kt @@ -13,7 +13,7 @@ fun SceneBuilderContext.songStreitDerVampire() { lightStep(StepCue.MusicStart("Streit der Vampire", 1.minutes + 40.seconds)) { BlinderBars.all { it.effect.static(StairvilleSplb.Effect.CHASE_INWARDS) - it.presetSpeed.static(40.percent) + it.effect.speed.static(40.percent) it.brightness.fade(25.percent, 1.seconds) } diff --git a/src/main/kotlin/de/moritzruth/theaterdsl/device/EffectDV.kt b/src/main/kotlin/de/moritzruth/theaterdsl/device/EffectDV.kt new file mode 100644 index 0000000..0e5d2fd --- /dev/null +++ b/src/main/kotlin/de/moritzruth/theaterdsl/device/EffectDV.kt @@ -0,0 +1,38 @@ +package de.moritzruth.theaterdsl.device + +import de.moritzruth.theaterdsl.value.percent +import kotlin.math.roundToLong +import kotlin.time.TimeMark +import kotlin.time.TimeSource + +abstract class EffectDV(private val initialEffect: T): DynamicValue { + private var time = 0L + private var effect: T = initialEffect + private var lastProgressMark: TimeMark? = null + val speed = PercentageDV(100.percent) + + fun static(effect: T) { + this.effect = effect + } + + override fun reset() { + time = 0 + effect = initialEffect + lastProgressMark = null + speed.reset() + } + + override fun skipTransition() { + speed.skipTransition() + } + + override fun getCurrentValue(): R { + val delta = ((lastProgressMark?.elapsedNow()?.inWholeMilliseconds ?: 0) * speed.getCurrentValue().value).roundToLong() + time += delta + lastProgressMark = TimeSource.Monotonic.markNow() + + return compute(time, effect) + } + + abstract fun compute(time: Long, effect: T?): R +} \ No newline at end of file diff --git a/src/main/kotlin/de/moritzruth/theaterdsl/value/Color.kt b/src/main/kotlin/de/moritzruth/theaterdsl/value/Color.kt index 4b71d43..c8d14e9 100644 --- a/src/main/kotlin/de/moritzruth/theaterdsl/value/Color.kt +++ b/src/main/kotlin/de/moritzruth/theaterdsl/value/Color.kt @@ -1,12 +1,16 @@ package de.moritzruth.theaterdsl.value import kotlin.math.abs +import kotlin.math.pow data class Color( val hue: Angle, val saturation: Percentage = 100.percent, val brightness: Percentage = 100.percent ) { + fun multiplyBrightness(other: Percentage): Color = copy(brightness = brightness * other) + fun powBrightness(n: Double): Color = copy(brightness = Percentage(brightness.value.pow(n))) + fun getRGB(): Triple { val c = brightness.value * saturation.value val h = hue.degrees / 60f diff --git a/src/main/kotlin/de/moritzruth/theaterdsl/value/Percentage.kt b/src/main/kotlin/de/moritzruth/theaterdsl/value/Percentage.kt index 985680c..e590847 100644 --- a/src/main/kotlin/de/moritzruth/theaterdsl/value/Percentage.kt +++ b/src/main/kotlin/de/moritzruth/theaterdsl/value/Percentage.kt @@ -19,6 +19,8 @@ value class Percentage(val value: Double) : Comparable { override fun toString(): String = "${value * 100}%" + operator fun times(other: Percentage) = Percentage(value * other.value) + class Range( override val start: Percentage, override val endInclusive: Percentage