aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-03-19 17:48:09 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-20 13:35:18 +0000
commit5e0c6b9b935b7b30e36579f812105472cfd6924e (patch)
tree53ac9d324261ee7510b5025dc29cebb188b83f25
parentlib: add osc/dump (diff)
downloadalive-5e0c6b9b935b7b30e36579f812105472cfd6924e.tar.gz
alive-5e0c6b9b935b7b30e36579f812105472cfd6924e.zip
lib: add ad, decay to link-time
-rw-r--r--alv-lib/link-time.moon179
1 files changed, 139 insertions, 40 deletions
diff --git a/alv-lib/link-time.moon b/alv-lib/link-time.moon
index 34e2220..251ee6c 100644
--- a/alv-lib/link-time.moon
+++ b/alv-lib/link-time.moon
@@ -97,6 +97,8 @@ every = Constant.meta
if post < pre
@out\set evt
+ vis: => type: 'event'
+
lfo = Constant.meta
meta:
name: 'lfo'
@@ -139,39 +141,6 @@ lfo = Constant.meta
vis: => type: 'bar', bar: @.out!
-ramp = Constant.meta
- meta:
- name: 'ramp'
- summary: "Sawtooth LFO."
- examples: { '(ramp [clock] period [phase] [max])' }
- description: "Ramps from `0` to `max` once every `period` beats.
-
-- `clock` should be a `time/clock!` stream. This argument can be omitted
- and the stream be passed as a dynamic definition in `*clock*` instead.
-- `period` should be a num~ stream.
-- `phase` should be a num~ stream and defaults to 0.
-- `max` should be a num~ stream and defaults to `period` if omitted."
- value: class extends Op
- pattern = -sig['link/clock'] + sig.num + -sig.num + -sig.num
- setup: (inputs, scope) =>
- { clock, period, phase, max } = pattern\match inputs
- super
- clock: Input.hot clock or scope\get '*clock*'
- period: Input.cold period
- phase: Input.cold phase or Constant.num 0
- max: max and Input.cold max
-
- @setup_out '~', T.num, 0
-
- tick: =>
- { :clock, :period, :phase, :max } = @unwrap_all!
- t = clock.state\phase_at_time clock.time, period
- t = (t - phase) / period % 1
- max or= period
- @out\set t * max
-
- vis: => type: 'bar', bar: @state
-
tick = Constant.meta
meta:
name: 'tick'
@@ -214,20 +183,20 @@ phase = Constant.meta
meta:
name: 'phase'
summary: "Get phase."
- examples: { '(tick [clock] period)' }
- description: "Counts upwards by one every `period` beats.
+ examples: { '(tick [clock] period [phase])' }
+ description: "Ramps from `0` to `period` once every `period` beats.
- `clock` should be a `link/clock~` stream. This argument can be omitted
and the stream be passed as a dynamic definition in `*clock*` instead.
-- `period` should be a num~ stream and defaults to 1.
-- returns a `num~` stream that ticks from 0 to `period`."
+- `period` should be a num~ stream.
+- `phase` should be a num~ stream and defaults to 0."
value: class extends Op
- pattern = -sig['link/clock'] + -sig.num + -sig.num
+ pattern = -sig['link/clock'] + sig.num + -sig.num
setup: (inputs, scope) =>
{ clock, period, phase } = pattern\match inputs
super
clock: Input.hot clock or scope\get '*clock*'
- period: Input.cold period or Constant.num 0
+ period: Input.cold period
phase: Input.cold phase or Constant.num 0
@setup_out '~', T.num
@@ -241,8 +210,136 @@ phase = Constant.meta
@out\set t
- vis: => type: 'event'
+ vis: => type: 'bar', bar: @.out! / @inputs.period!
+
+ramp = Constant.meta
+ meta:
+ name: 'ramp'
+ summary: "Get normalized phase."
+ examples: { '(ramp [clock] period [phase])' }
+ description: "Ramps from `0` to `1` once every `period` beats.
+
+- `clock` should be a `time/clock!` stream. This argument can be omitted
+ and the stream be passed as a dynamic definition in `*clock*` instead.
+- `period` should be a num~ stream.
+- `phase` should be a num~ stream and defaults to 0."
+ value: class extends Op
+ pattern = -sig['link/clock'] + sig.num + -sig.num
+ setup: (inputs, scope) =>
+ { clock, period, phase } = pattern\match inputs
+ super
+ clock: Input.hot clock or scope\get '*clock*'
+ period: Input.cold period
+ phase: Input.cold phase or Constant.num 0
+
+ @setup_out '~', T.num
+
+ tick: =>
+ { :clock, :period, :phase, :evt } = @unwrap_all!
+ return if period == 0
+
+ t = clock.state\phase_at_time clock.time, period
+ t = (t + period - phase) % period
+
+ @out\set t / period
+
+ vis: => type: 'bar', bar: @.out!
+
+decay = Constant.meta
+ meta:
+ name: 'decay'
+ summary: "Decay envelope from bang!."
+ examples: { '(decay [clock] dur trigger)' }
+ description: "Ramp from `1` to `0` in `dur` beats on `trigger`.
+
+- `clock` should be a `link/clock~` stream. This argument can be omitted
+ and the stream be passed as a dynamic definition in `*clock*` instead.
+- `dur` should be a num~ stream.
+- `trigger` should be a bang! stream."
+ value: class extends Op
+ pattern = -sig['link/clock'] + sig.num + evt.bang
+ setup: (inputs, scope) =>
+ { clock, dur, trigger } = pattern\match inputs
+ super
+ clock: Input.hot clock or scope\get '*clock*'
+ dur: Input.cold dur
+ trigger: Input.hot trigger
+
+ @setup_out '~', T.num, 0
+ @state = math.min @inputs.dur!, (@state or 0)
+
+ tick: =>
+ { :clock, :dur, :trigger } = @unwrap_all!
+ if trigger
+ @state = dur
+ elseif clock
+ return if @state <= 0
+ pre = clock.state\beat_at_time clock.time - clock.dt, 1
+ post = clock.state\beat_at_time clock.time, 1
+ delta = post - pre
+ @state = math.max 0, @state - delta
+
+ @out\set @state / dur
+
+ vis: => type: 'bar', bar: @.out!
+
+ad = Constant.meta
+ meta:
+ name: 'ad'
+ summary: "Attack-Decay enveloper from bang!."
+ examples: { '(ad [clock] attack decay trigger)' }
+ description: "Ramp from `0` to `1` and back on `trigger`.
+
+- `clock` should be a `link/clock~` stream. This argument can be omitted
+ and the stream be passed as a dynamic definition in `*clock*` instead.
+- `attack` and `decay` should be num~ streams.
+- `trigger` should be a bang! stream."
+ value: class extends Op
+ pattern = -sig['link/clock'] + sig.num + sig.num + evt.bang
+ setup: (inputs, scope) =>
+ { clock, attack, decay, trigger } = pattern\match inputs
+ super
+ clock: Input.hot clock or scope\get '*clock*'
+ attack: Input.cold attack
+ decay: Input.cold decay
+ trigger: Input.hot trigger
+
+ @setup_out '~', T.num, 0
+ @state = math.min @inputs.decay!, math.max -@inputs.attack!, (@state or 0)
+
+ tick: =>
+ { :clock, :attack, :decay, :trigger } = @unwrap_all!
+
+ delta = 0
+
+ if trigger
+ @state = -attack
+ elseif clock
+ return if @state == 0
+
+ pre = clock.state\beat_at_time clock.time - clock.dt, 1
+ post = clock.state\beat_at_time clock.time, 1
+ delta = post - pre
+
+ if @state < 0
+ -- attack phase
+ @state = @state + delta
+
+ if @state >= 0
+ -- fall through to decay phase with remaining delta
+ delta = @state
+ @state = decay
+ else
+ @out\set 1 + @state / attack
+ return
+
+ if @state > 0
+ -- decay phase
+ @state = math.max 0, @state - delta
+ @out\set @state / decay
+
+ vis: => type: 'bar', bar: @.out!
Constant.meta
meta:
@@ -256,3 +353,5 @@ Constant.meta
:ramp
:tick
:phase
+ :decay
+ :ad