130 lines
3.4 KiB
Kotlin
130 lines
3.4 KiB
Kotlin
package org.openrndr.extra.timeoperators
|
|
|
|
import org.openrndr.extra.parameters.BooleanParameter
|
|
import org.openrndr.extra.parameters.DoubleParameter
|
|
import org.openrndr.math.clamp
|
|
import org.openrndr.math.mix
|
|
import kotlin.math.max
|
|
import kotlin.math.min
|
|
import kotlin.math.pow
|
|
|
|
|
|
enum class EnvelopePhase {
|
|
Rest, Attack, Decay
|
|
}
|
|
|
|
// Exponential Ease-In and Ease-Out by Golan Levin
|
|
// http://www.flong.com/texts/code/shapers_exp/
|
|
private fun exponentialEasing(x: Double, a: Double): Double {
|
|
var a = a
|
|
val epsilon = 0.00001
|
|
val minParamA = (0.0 + epsilon)
|
|
val maxParamA = (1.0 - epsilon)
|
|
a = max(minParamA, min(maxParamA, a))
|
|
|
|
return if (a < 0.5) {
|
|
// emphasis
|
|
a = (2.0 * a)
|
|
x.pow(a)
|
|
} else {
|
|
// de-emphasis
|
|
a = (2.0 * (a - 0.5))
|
|
x.pow(1.0 / (1 - a))
|
|
}
|
|
}
|
|
|
|
class Envelope(
|
|
var restValue: Double = 0.0,
|
|
var targetValue: Double = 1.0,
|
|
@DoubleParameter("Attack Duration", 0.0, 5.0, 3, 0)
|
|
var attack: Double = 0.3,
|
|
@DoubleParameter("Decay Duration", 0.0, 5.0, 3, 1)
|
|
var decay: Double = 0.5,
|
|
@DoubleParameter("Easing Factor", 0.0, 1.0, 3, 2)
|
|
var easingFactor: Double = 0.3,
|
|
@BooleanParameter("Re-trigger", 3)
|
|
var reTrigger: Boolean = false
|
|
) : TimeTools
|
|
{
|
|
var phase = EnvelopePhase.Rest
|
|
set(value) {
|
|
if (value == EnvelopePhase.Rest) {
|
|
initialTime = Double.NEGATIVE_INFINITY
|
|
current = initialRestValue
|
|
}
|
|
|
|
field = value
|
|
}
|
|
|
|
val value: Double
|
|
get() {
|
|
return current
|
|
}
|
|
|
|
private var initialTime = Double.NEGATIVE_INFINITY
|
|
private var current = restValue
|
|
private var initialRestValue = restValue
|
|
private val cycleDuration: Double
|
|
get() {
|
|
return attack + decay
|
|
}
|
|
|
|
override fun tick(seconds: Double, deltaTime: Double, frameCount: Int) {
|
|
if (phase == EnvelopePhase.Rest) return
|
|
|
|
// TODO: what happens if deltaTime < cycleDuration?
|
|
|
|
if (initialTime == Double.NEGATIVE_INFINITY) {
|
|
initialTime = seconds
|
|
}
|
|
|
|
val cycleTime = seconds - initialTime
|
|
|
|
if (cycleTime < 0) {
|
|
phase = EnvelopePhase.Rest
|
|
return
|
|
}
|
|
|
|
if (cycleTime <= attack) {
|
|
phase = EnvelopePhase.Attack
|
|
} else if (cycleTime > attack && cycleTime < cycleDuration) {
|
|
phase = EnvelopePhase.Decay
|
|
} else {
|
|
phase = EnvelopePhase.Rest
|
|
return
|
|
}
|
|
|
|
if (phase == EnvelopePhase.Attack) {
|
|
val denom = if (attack == 0.0) 1.0 else attack
|
|
|
|
val t = clamp(cycleTime / denom, 0.0, 1.0)
|
|
|
|
current = mix(restValue, targetValue, exponentialEasing(t, easingFactor))
|
|
}
|
|
|
|
if (phase == EnvelopePhase.Decay) {
|
|
val denom = if (decay == 0.0) 1.0 else decay
|
|
|
|
val t = clamp((cycleTime - attack) / denom, 0.0, 1.0)
|
|
|
|
current = mix(targetValue, initialRestValue, exponentialEasing(t, easingFactor))
|
|
}
|
|
}
|
|
|
|
fun trigger(value: Double = targetValue) {
|
|
restValue = if (initialTime != Double.NEGATIVE_INFINITY) {
|
|
current
|
|
} else {
|
|
initialRestValue
|
|
}
|
|
|
|
if (reTrigger) {
|
|
restValue = initialRestValue
|
|
}
|
|
|
|
initialTime = Double.NEGATIVE_INFINITY
|
|
phase = EnvelopePhase.Attack
|
|
|
|
targetValue = value
|
|
}
|
|
} |