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 [sync-mode] [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. - `sync-mode` has to be a bool or str and defaults to `false`: - `\"none\"` / `false`: no syncing (link disabled) - `\"tempo\"`: tempo sync - `\"full\"`: tempo and start/stop sync (starts stopped) - `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.str) + -sig.num + -sig.num)\match inputs super mode: 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'] if not @state @state = { link: al.create @inputs.bpm! state: al.create_session_state! } @state.time or= @state.link\clock_micros! poll: => time = @state.link\clock_micros! dt = time - @state.time ft = 1000*1000 / @inputs.fps! if dt >= ft and not @inputs.io.result\dirty! @inputs.io.result\set true true tick: (setup) => { :link, :state } = @state link\capture_app_session_state state time = link\clock_micros! dirty = false if @inputs.bpm\dirty! state\set_tempo @inputs.bpm!, time dirty = true if @inputs.mode\dirty! mode = @inputs.mode! link\enable_start_stop_sync mode == "full" link\enable mode != "none" and mode != false if mode != "full" state\set_is_playing true, time dirty = true if dirty link\commit_app_session_state state if setup @out\set :time, :state, dt: 0 else if @inputs.io\dirty! if state\is_playing! dt = time - @state.time @out\set :time, :state, :dt @state.time = time 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 vis: => type: 'event' 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! 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' phase = Constant.meta meta: name: 'phase' summary: "Get phase." 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. - `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 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 = if attack > 0 then -attack else decay 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! 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 `link/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 (beats)" value: class extends Op pattern = -evt['link/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! evt = { i: 0 amp: evt.amp or 1 dur: evt.dur or 1 -- ease: evt.ease or "quad.out" } @state.events[evt] = true if clock = @inputs.clock! pre = clock.state\beat_at_time clock.time - clock.dt, 1 post = clock.state\beat_at_time clock.time, 1 delta = post - pre accum = 0 for evt in pairs @state.events evt.i += delta / evt.dur if evt.i >= 1 @state.offset += evt.amp @state.events[evt] = nil elseif evt.i >= 0 t = evt.i -- quad.out -- t = 1 - t -- t = 1 - t * t accum += t * evt.amp @out\set @state.offset + accum fade = Constant.meta meta: name: 'fade' summary: "A one-time transition" examples: { '(fade [clock] duration [to])' } description: " Linear fade from last to `to` over `duration` beats whenever `to` changes. There is no transition on first evaluation." value: class extends Op pattern = -evt.clock + sig.num + -sig.num + -sig.num setup: (inputs, scope) => { clock, duration, to } = pattern\match inputs super clock: Input.hot clock or scope\get '*clock*' duration: Input.cold duration to: Input.hot to or T.num\mk_const 1 @setup_out '~', T.num, @inputs.to! tick: (setup) => { :clock, :duration, :to } = @unwrap_all! if @inputs.to\dirty! @state = frm: @.out! if clock @state.beats or= clock.state\beat_at_time clock.time - clock.dt, 1 beats = clock.state\beat_at_time clock.time, 1 t = math.min 1, (beats - @state.beats) / duration @out\set @state.frm + t * (to - @state.frm) Constant.meta meta: name: 'link-time' summary: "Musical time with Ableton Link." value: :clock :every :lfo :ramp :tick :phase :decay :ad :spring :fade