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