aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2024-11-13 18:47:56 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-02 14:24:49 +0000
commitbe796c2988677c22f4cb76ee848efdc7153545ef (patch)
tree3f099f5e7834d5348be5ca92ca47e2400d18ee77
parentlib: add // to math (diff)
downloadalive-be796c2988677c22f4cb76ee848efdc7153545ef.tar.gz
alive-be796c2988677c22f4cb76ee848efdc7153545ef.zip
lib: add time/spring
-rw-r--r--alv-lib/time.moon82
1 files changed, 81 insertions, 1 deletions
diff --git a/alv-lib/time.moon b/alv-lib/time.moon
index beaeac0..1f8e324 100644
--- a/alv-lib/time.moon
+++ b/alv-lib/time.moon
@@ -28,7 +28,7 @@ frame rate.
dt = time - @state
ft = 1 / @inputs.fps!
- if dt >= ft
+ if dt >= ft and not @inputs.io.result\dirty!
@inputs.io.result\set true
true
@@ -353,6 +353,85 @@ Creates smooth transitions when `value` changes.
return if 1e-15 > math.abs delta
@out\set current + delta * rate
+spring = Constant.meta
+ meta:
+ name: 'spring'
+ summary: "Integrate impulses using easing"
+ examples: { '(spring [clock] evt0 [evt1…])' }
+ description: "
+Every arriving `evt` triggers an impulse that is integrated as `clock` ticks.
+Returns a num~ stream that starts at 0.
+
+- `clock` should be a `time/clock!` stream. This argument can be omitted
+ and the stream be passed as a dynamic definition in `*clock*` instead.
+- `evt!` are !-streams of structs.
+ The following struct keys can be specified:
+ - `amp`: amplitude of the impulse
+ - `dur`: duration of the impulse (seconds)
+ - `del`: start delay (seconds)
+ - `ease`: easing function to use
+
+Instead of `dur`, `in` and `out` can be specified to create both an impulse
+and counterimpulse, which has an amplitude of `-amp` and is delayed so as to
+start immediately after the original impulse. Similarily, `easein` and `easeout`
+can be specified instead of the single `ease` value."
+
+ value: class extends Op
+ pattern = -evt.clock + evt!*0
+ setup: (inputs, scope) =>
+ { clock, events } = pattern\match inputs
+
+ super
+ clock: Input.hot clock or scope\get '*clock*'
+ events: [Input.hot e for e in *events]
+
+ @state or= { offset: 0, events: {} }
+ @setup_out '~', T.num, @state.offset
+
+ tick: =>
+ for inp in *@inputs.events
+ if evt = inp!
+ amp = evt.amp or 1
+ del = evt.del or 0
+ dur = evt.dur or 1
+
+ on = {
+ i: -del
+ amp: amp
+ dur: evt.in or evt.dur
+ ease: evt.easein or evt.ease or "quad.out"
+ }
+ on.i = on.i / on.dur
+ @state.events[on] = true
+
+ if evt.out
+ off = {
+ i: -(del + on.dur),
+ amp: -amp,
+ dur: evt.out
+ ease: evt.easeout or evt.ease or "quad.out"
+ }
+ off.i = off.i / off.dur
+ @state.events[off] = true
+
+ if clk = @inputs.clock!
+ dt = clk.dt
+ accum = 0
+
+ for evt in pairs @state.events
+ evt.i += dt / evt.dur
+
+ if evt.i >= 1
+ @state.offset += evt.amp
+ @state.events[evt] = nil
+ elseif evt.i >= 0
+ t = 1 - evt.i
+ t = 1 - t * t
+ accum += t * evt.amp
+
+ @out\set @state.offset + accum
+
+
delay = Constant.meta
meta:
name: 'delay!'
@@ -416,6 +495,7 @@ RTNode
'bang-seq': bang_seq
:smooth
+ :spring
'delay!': delay
'*clock*': default_clock