diff --git a/orx-envelopes/build.gradle.kts b/orx-envelopes/build.gradle.kts index b104cf60..ac1faec7 100644 --- a/orx-envelopes/build.gradle.kts +++ b/orx-envelopes/build.gradle.kts @@ -18,7 +18,8 @@ kotlin { @Suppress("UNUSED_VARIABLE") val jvmDemo by getting { dependencies { - implementation(project(":orx-shapes")) + implementation(project(":orx-envelopes")) + implementation(project(":orx-noise")) } } } diff --git a/orx-envelopes/src/commonMain/kotlin/ADSR.kt b/orx-envelopes/src/commonMain/kotlin/ADSR.kt index dd391d86..f84fcbad 100644 --- a/orx-envelopes/src/commonMain/kotlin/ADSR.kt +++ b/orx-envelopes/src/commonMain/kotlin/ADSR.kt @@ -8,11 +8,15 @@ data class ADSR( val decayDuration: Double, val sustainValue: Double, val releaseDuration: Double -) : Envelope{ +) : Envelope() { override fun value(t: Double, tOff: Double): Double { return adsr(attackDuration, decayDuration, sustainValue, releaseDuration, t, tOff) } + override fun position(t: Double, tOff: Double): Double { + return adsrPosition(attackDuration, decayDuration, releaseDuration, t, tOff) + } + override fun isActive(t: Double, tOff: Double): Boolean { return !(t - tOff > releaseDuration) } @@ -26,12 +30,21 @@ fun adsr( t: Double, tOff: Double = 1E10 ): Double { - val da = t / attackDuration val dc = (t - attackDuration) / decayDuration - val vOn = mix(min(1.0, da), sustainValue, dc.coerceIn(0.0..1.0)) - return mix(vOn, 0.0, ((t - tOff) / releaseDuration).coerceIn(0.0..1.0)) +} +fun adsrPosition( + attackDuration: Double, + decayDuration: Double, + releaseDuration: Double, + t: Double, + tOff: Double +): Double { + val ta = (t / attackDuration).coerceIn(0.0..1.0) + val td = ((t - attackDuration) / decayDuration).coerceIn(0.0..1.0) + val tr = ((t - tOff) / releaseDuration).coerceIn(0.0..1.0) + return (ta + td + tr) / 3.0 } \ No newline at end of file diff --git a/orx-envelopes/src/commonMain/kotlin/Envelope.kt b/orx-envelopes/src/commonMain/kotlin/Envelope.kt index 1886af2f..c4c0c6a2 100644 --- a/orx-envelopes/src/commonMain/kotlin/Envelope.kt +++ b/orx-envelopes/src/commonMain/kotlin/Envelope.kt @@ -1,7 +1,11 @@ package org.openrndr.extra.envelopes -interface Envelope { - fun value(t: Double, tOff: Double): Double +abstract class Envelope { + abstract fun value(t: Double, tOff: Double): Double - fun isActive(t: Double, tOff:Double): Boolean + abstract fun position(t: Double, tOff: Double): Double + + abstract fun isActive(t: Double, tOff: Double): Boolean + + var objectFunction: ((time: Double, value: Double, position: Double) -> Unit) = { _, _, _ -> } } \ No newline at end of file diff --git a/orx-envelopes/src/commonMain/kotlin/MPPSynchronize.kt b/orx-envelopes/src/commonMain/kotlin/MPPSynchronize.kt new file mode 100644 index 00000000..0a5f0b57 --- /dev/null +++ b/orx-envelopes/src/commonMain/kotlin/MPPSynchronize.kt @@ -0,0 +1,3 @@ +package org.openrndr.extra.envelopes + +expect fun mppSynchronized(lock:Any, f:()->V) : V \ No newline at end of file diff --git a/orx-envelopes/src/commonMain/kotlin/Tracker.kt b/orx-envelopes/src/commonMain/kotlin/Tracker.kt index 00719d06..7279942b 100644 --- a/orx-envelopes/src/commonMain/kotlin/Tracker.kt +++ b/orx-envelopes/src/commonMain/kotlin/Tracker.kt @@ -5,42 +5,62 @@ package org.openrndr.extra.envelopes import org.openrndr.Clock import org.openrndr.extra.parameters.DoubleParameter -class Trigger(val on: Double, var off: Double, val envelope: Envelope) +class Trigger(val id: Int, val on: Double, var off: Double, val envelope: Envelope) -class TrackerValue(val time: Double, val value: Double) -abstract class Tracker(val clock: Clock) { - - val triggers = mutableListOf() - - - protected abstract fun createEnvelope(): T - - fun triggerOn() { - val t = clock.seconds - triggers.removeAll { !it.envelope.isActive(t - it.on, it.off - it.on) } - triggers.add(Trigger(clock.seconds, 1E30, createEnvelope())) +class TrackerValue( + val time: Double, + val value: Double, + val position: Double, + val envelope: Envelope +) { + operator fun invoke() { + draw() } - fun triggerOff() { - val t = clock.seconds - triggers.removeAll { !it.envelope.isActive(t - it.on, it.off - it.on) } - triggers.lastOrNull()?.let { - it.off = clock.seconds + fun draw() { + envelope.objectFunction(time, value, position) + } +} + +abstract class Tracker(val clock: Clock) { + val triggers = mutableListOf() + + protected abstract fun createEnvelope(objectFunction: (time: Double, value: Double, position: Double) -> Unit): T + + fun triggerOn( + triggerId: Int = 0, + objectFunction: (time: Double, value: Double, position: Double) -> Unit = { _, _, _ -> } + ) { + mppSynchronized(triggers) { + val t = clock.seconds + triggers.removeAll { !it.envelope.isActive(t - it.on, it.off - it.on) } + triggers.add(Trigger(triggerId, clock.seconds, 1E30, createEnvelope(objectFunction))) + } + } + + fun triggerOff(triggerId: Int = 0) { + mppSynchronized(triggers) { + val t = clock.seconds + triggers.removeAll { !it.envelope.isActive(t - it.on, it.off - it.on) } + triggers.findLast { it.id == triggerId }?.let { + it.off = clock.seconds + } } } fun values(): List { val t = clock.seconds - return triggers.mapNotNull { - val tOn = t - it.on - val tOff = it.off - it.on + return mppSynchronized(triggers) { + triggers.mapNotNull { + val tOn = t - it.on + val tOff = it.off - it.on - if (it.envelope.isActive(tOn, tOff)) { - val v = it.envelope.value(tOn, tOff) - - TrackerValue(t, v) - } else { - null + if (it.envelope.isActive(tOn, tOff)) { + val v = it.envelope.value(tOn, tOff) + TrackerValue(t, v, it.envelope.position(tOn, tOff), it.envelope) + } else { + null + } } } } @@ -48,21 +68,27 @@ abstract class Tracker(val clock: Clock) { fun value(): Double { return values().sumOf { it.value } } - } -class ADSRTracker(clock: Clock): Tracker(clock) { + +class ADSRTracker(clock: Clock) : Tracker(clock) { @DoubleParameter("attack", 0.0, 20.0, order = 1) var attack: Double = 0.1 + @DoubleParameter("decay", 0.0, 20.0, order = 2) var decay: Double = 0.1 + @DoubleParameter("sustain", 0.0, 1.0, order = 3) var sustain: Double = 0.9 + @DoubleParameter("release", 0.0, 20.0, order = 4) var release: Double = 0.9 - override fun createEnvelope(): ADSR { - return ADSR(attack, decay, sustain, release) + override fun createEnvelope(objectFunction: (time: Double, value: Double, position: Double) -> Unit): ADSR { + return ADSR(attack, decay, sustain, release).apply { + this.objectFunction = objectFunction + } } -} \ No newline at end of file +} + diff --git a/orx-envelopes/src/jsMain/kotlin/MPPSynchronize.kt b/orx-envelopes/src/jsMain/kotlin/MPPSynchronize.kt new file mode 100644 index 00000000..23bf70ac --- /dev/null +++ b/orx-envelopes/src/jsMain/kotlin/MPPSynchronize.kt @@ -0,0 +1,5 @@ +package org.openrndr.extra.envelopes + +actual fun mppSynchronized(lock: Any, f: () -> V): V { + return f() +} \ No newline at end of file diff --git a/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt b/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt new file mode 100644 index 00000000..21d250f6 --- /dev/null +++ b/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt @@ -0,0 +1,46 @@ +import org.openrndr.application +import org.openrndr.draw.loadFont +import org.openrndr.extra.envelopes.ADSRTracker +import org.openrndr.extra.noise.uniform +import org.openrndr.shape.Rectangle + +fun main() { + application { + program { + val tracker = ADSRTracker(this) + tracker.attack = 1.0 + tracker.decay = 0.2 + tracker.sustain = 0.8 + tracker.release = 2.0 + + keyboard.keyDown.listen { + if (it.name == "t") { + val center = drawer.bounds.uniform(distanceToEdge = 30.0) + tracker.triggerOn(0) { time, value, position -> + drawer.circle(center, value * 100.0) + } + } + if (it.name == "r") { + val center = drawer.bounds.uniform(distanceToEdge = 30.0) + tracker.triggerOn(1) { time, value, position -> + val r = Rectangle.fromCenter(center, width = value * 100.0, height = value * 100.0) + drawer.rectangle(r) + } + } + } + keyboard.keyUp.listen { + if (it.name == "t") + tracker.triggerOff(0) + if (it.name == "r") + tracker.triggerOff(1) + } + extend { + tracker.values().forEach { + it() + } + drawer.fontMap = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 16.0) + drawer.text("press and hold 't' and/or 'r'", 20.0, height - 20.0) + } + } + } +} \ No newline at end of file diff --git a/orx-envelopes/src/jvmMain/kotlin/MPPSynchronize.kt b/orx-envelopes/src/jvmMain/kotlin/MPPSynchronize.kt new file mode 100644 index 00000000..b23742f3 --- /dev/null +++ b/orx-envelopes/src/jvmMain/kotlin/MPPSynchronize.kt @@ -0,0 +1,7 @@ +package org.openrndr.extra.envelopes + +actual fun mppSynchronized(lock: Any, f: () -> V): V { + return synchronized(lock) { + f() + } +} \ No newline at end of file