diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-03-14 23:45:49 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-03-15 14:14:04 +0000 |
| commit | 8526e533c9f8d16495c248f2fd9c0b224b267d36 (patch) | |
| tree | 1862a442fef10866ef641ee465c84cf37bdcee1d | |
| parent | implement template strings (diff) | |
| download | alive-8526e533c9f8d16495c248f2fd9c0b224b267d36.tar.gz alive-8526e533c9f8d16495c248f2fd9c0b224b267d36.zip | |
lib: add link-time module
| -rw-r--r-- | alv-lib/link-time.moon | 224 | ||||
| -rw-r--r-- | docs/gen/shim.moon | 2 |
2 files changed, 225 insertions, 1 deletions
diff --git a/alv-lib/link-time.moon b/alv-lib/link-time.moon new file mode 100644 index 0000000..1568b49 --- /dev/null +++ b/alv-lib/link-time.moon @@ -0,0 +1,224 @@ +import Constant, Op, Input, T, any, sig, evt from require 'alv.base' +al = require 'abletonlink' + +clock = Constant.meta + meta: + name: 'clock' + summary: "Create a clock source." + examples: { '(clock [synced] [bpm] [fps])' } + description: "Creates a new `link/clock~` stream. + +The clock event stream is an IO that triggers other operators at a fixed +frame rate. + +- `synced` has to be a bool~ constant and defaults to `false`. +- `bpm` should be a num~ stream and defaults to `120`. +- `fps` has to be a num= constant and defaults to `60` if omitted." + value: class extends Op + setup: (inputs) => + { synced, bpm, fps } = (sig.bool + -sig.num + -sig.num)\match inputs + super + synced: Input.hot synced or Constant.bool false + bpm: Input.hot bpm or Constant.num 120 + fps: Input.hot fps or Constant.num 60 + + io: Input.hot T.bang\mk_evt! + + @setup_out '~', T['link/clock'] + @state or= { + link: al.create 120 + state: al.create_session_state! + } + @state.time or= @state.link\clock_micros! + + poll: => + time = @state.link\clock_micros! + dt = time - @state.time + ft = 1 / @inputs.fps! + + if dt >= ft and not @inputs.io.result\dirty! + @state.state = al.create_session_state! + @state.link\capture_app_session_state @state.state + @inputs.io.result\set true + true + + tick: (setup) => + { :link, :time, :state } = @state + + if @inputs.synced\dirty! + link\enable @inputs.synced! + + if @inputs.bpm\dirty! + state\set_tempo @inputs.bpm!, time + + link\commit_app_session_state state + + if setup or @inputs.io\dirty! + time = link\clock_micros! + dt = time - @state.time + @state.time = time + @out\set :time, :state, :dt + +every = Constant.meta + meta: + name: 'every' + summary: "Emit events regularly." + examples: { '(every [clock] period [phase] [evt])' } + description: "Emits `evt` as an event 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. +- `phase` should be a num~ stream and defaults to 0. +- `evt` can be a value of any type. It defaults to `bang`. +- the return type will be an event stream with the same type as `evt`." + value: class extends Op + pattern = -sig['link/clock'] + sig.num + -sig.num + -sig! + setup: (inputs, scope) => + { clock, period, phase, evt } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period + phase: Input.cold phase or Constant.num 0 + evt: Input.cold evt or T.bang\mk_const true + + @setup_out '!', @inputs.evt\type! + + tick: => + { :clock, :period, :phase, :evt } = @unwrap_all! + return if period == 0 + + pre = clock.state\phase_at_time clock.time - clock.dt, period + post = clock.state\phase_at_time clock.time, period + + pre = (pre + period - phase) % period + post = (post + period - phase) % period + + if post < pre + @out\set evt + +lfo = Constant.meta + meta: + name: 'lfo' + summary: "Low-frequency oscillator." + examples: { '(lfo [clock] period [phase] [wave])' } + description: "Oscillates betwen `0` and `1` 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. +- `freq` should be a num~ stream. +- `phase` should be a num~ stream and defaults to 0. +- `wave` selects the wave shape from one of the following: + - `'sin'` (the default) + - `'saw'` + - `'tri'`" + value: class extends Op + pattern = -sig['link/clock'] + sig.num + -sig.num + -sig.str + setup: (inputs, scope) => + { clock, period, phase, wave } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period + phase: Input.cold phase or Constant.num 0 + wave: Input.cold wave or Constant.str 'sin' + + @state or= 0 + @setup_out '~', T.num + + tau = math.pi * 2 + tick: => + { :clock, :period, :phase, :wave } = @unwrap_all! + t = clock.state\phase_at_time clock.time, period + t = (t - phase) / period % 1 + + @out\set switch wave + when 'sin' then .5 + .5 * math.cos t * tau + when 'saw' then t + when 'tri' then math.abs (2*t % 2) - 1 + else error Error 'argument', "unknown wave type '#{wave}'" + + 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' + summary: "Count ticks." + examples: { '(tick [clock] period [phase])' } + description: "Counts upwards by one 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. +- `phase` should be a num~ stream and defaults to 0. +- returns a `num~` stream that increases by 1 every `period`." + 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 or Constant.num 0 + phase: Input.cold phase or Constant.num 0 + + @setup_out '~', T.num, 0 + + tick: => + { :clock, :period, :phase, :evt } = @unwrap_all! + return if period == 0 + + pre = clock.state\phase_at_time clock.time - clock.dt, period + post = clock.state\phase_at_time clock.time, period + + pre = (pre + period - phase) % period + post = (post + period - phase) % period + + if post < pre + @out\set @.out! + 1 + + vis: => type: 'event' + + +Constant.meta + meta: + name: 'link-time' + summary: "Musical time with Ableton Link." + + value: + :clock + :every + :lfo + :ramp + :tick diff --git a/docs/gen/shim.moon b/docs/gen/shim.moon index 46da0ac..e8a4f08 100644 --- a/docs/gen/shim.moon +++ b/docs/gen/shim.moon @@ -4,7 +4,7 @@ export require require = do old_require = require - blacklist = {k, true for k in *{'losc', 'socket', 'system', 'luartmidi'}} + blacklist = {k, true for k in *{'losc', 'socket', 'system', 'luartmidi', 'abletonlink'}} (mod, ...) -> return {} if blacklist[mod] old_require mod, ... |
