aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-21 19:06:18 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-21 19:06:18 +0000
commite83df1af2cdad8c2d61ba790a96875cd260eceaf (patch)
tree5766654329675bd5259be772bc2537c7fdd8ab1c /lib
parentdocs/guide: document supported interpreter versions (diff)
downloadalive-e83df1af2cdad8c2d61ba790a96875cd260eceaf.tar.gz
alive-e83df1af2cdad8c2d61ba790a96875cd260eceaf.zip
new meta/doc system
Diffstat (limited to 'lib')
-rw-r--r--lib/logic.moon159
-rw-r--r--lib/math.moon218
-rw-r--r--lib/midi.moon169
-rw-r--r--lib/midi/core.moon70
-rw-r--r--lib/midi/launchctl.moon210
-rw-r--r--lib/osc.moon102
-rw-r--r--lib/pilot.moon90
-rw-r--r--lib/random.moon65
-rw-r--r--lib/sc.moon49
-rw-r--r--lib/string.moon17
-rw-r--r--lib/time.moon267
-rw-r--r--lib/util.moon188
12 files changed, 897 insertions, 707 deletions
diff --git a/lib/logic.moon b/lib/logic.moon
index 7264be0..157d190 100644
--- a/lib/logic.moon
+++ b/lib/logic.moon
@@ -1,4 +1,4 @@
-import Op, Input, Error, match from require 'core.base'
+import Op, Value, Input, Error, match from require 'core.base'
all_same = (first, list) ->
for v in *list
@@ -31,77 +31,98 @@ class ReduceOp extends Op
@out\set accum
-class eq extends Op
- @doc: "(eq a b [c]...)
-(== a b [c]...) - check for equality
+eq = Value.meta
+ meta:
+ name: 'eq'
+ summary: "Check for equality."
+ examples: { '(== a b [c]...)', '(eq a b [c]...)' }
+ description: "`true` if the types and values of all arguments are equal."
+
+ value: class extends Op
+ new: => super 'bool', false
+
+ setup: (inputs) =>
+ { first, rest } = match "any *any", inputs
+ same = all_same first\type!, [i\type! for i in *rest]
+
+ super if same
+ {
+ first: Input.value first
+ rest: [Input.value v for v in *rest]
+ }
+ else
+ {}
+
+ tick: =>
+ if not @inputs.first
+ @out\set false
+ return
+
+ { :first, :rest } = @unwrap_all!
+
+ equal = true
+ for other in *rest
+ if first != other
+ equal = false
+ break
-If the value types dont match, the result is an eval-time constant 'false'."
+ @out\set equal
- new: => super 'bool', false
- setup: (inputs) =>
- { first, rest } = match "any *any", inputs
- same = all_same first\type!, [i\type! for i in *rest]
+not_eq = Value.meta
+ meta:
+ name: 'not-eq'
+ summary: "Check for inequality."
+ examples: { '(!= a b [c]...)', '(not-eq a b [c]...)' }
+ description: "`true` if types or values of any two arguments are different."
- super if same
- {
- first: Input.value first
- rest: [Input.value v for v in *rest]
- }
- else
- {}
+ value: class extends Op
+ new: => super 'bool'
- tick: =>
- if not @inputs.first
- @out\set false
- return
+ setup: (inputs) =>
+ assert #inputs > 1, Error 'argument', "need at least two values"
+ super [Input.value v for v in *inputs]
- { :first, :rest } = @unwrap_all!
+ tick: =>
+ if not @inputs[1]
+ @out\set true
+ return
- equal = true
- for other in *rest
- if first != other
- equal = false
- break
+ diff = true
+ for a=1, #@inputs-1
+ for b=a+1, #@inputs
+ if @inputs[a].stream == @inputs[b].stream
+ diff = false
+ break
- @out\set equal
+ break unless diff
-class not_eq extends Op
- @doc: "(not-eq a b [c]...)
-(!= a b [c]...) - check for inequality"
- new: => super 'bool'
+ @out\set diff
- setup: (inputs) =>
- assert #inputs > 1, Error 'argument', "need at least two values"
- super [Input.value v for v in *inputs]
+and_ = Value.meta
+ meta:
+ name: 'and'
+ summary: "Logical AND."
+ examples: { '(and a b [c…])' }
+ value: class extends ReduceOp
+ fn: (a, b) -> a and b
- tick: =>
- if not @inputs[1]
- @out\set true
- return
-
- diff = true
- for a=1, #@inputs-1
- for b=a+1, #@inputs
- if @inputs[a].stream == @inputs[b].stream
- diff = false
- break
+or_ = Value.meta
+ meta:
+ name: 'or'
+ summary: "Logical OR."
+ examples: { '(or a b [c…])' }
+ value: class extends ReduceOp
+ fn: (a, b) -> a or b
- break unless diff
+not_ = Value.meta
+ meta:
+ name: 'not'
+ summary: "Logical NOT."
+ examples: { '(not a)' }
- @out\set diff
-
-class and_ extends ReduceOp
- @doc: "(and a b [c]...) - AND values"
- fn: (a, b) -> a and b
-
-class or_ extends ReduceOp
- @doc: "(or a b [c]...) - OR values"
- fn: (a, b) -> a or b
-
-class not_ extends Op
- @doc: "(not a) - boolean opposite"
- new: => super 'bool'
+ value: class extends Op
+ new: => super 'bool'
setup: (inputs) =>
{ value } = match 'any', inputs
@@ -109,15 +130,21 @@ class not_ extends Op
tick: => @out\set not tobool @inputs.value!
-class bool extends Op
- @doc: "(bool a) - convert to bool"
- new: => super 'bool'
+bool = Value.meta
+ meta:
+ name: 'bool'
+ summary: "Cast value to bool."
+ examples: { '(bool a)' }
+ description: "`false` if a is `false`, `nil` or `0`, `true` otherwise."
- setup: (inputs) =>
- { value } = match 'any', inputs
- super value: Input.value value
+ value: class extends Op
+ new: => super 'bool'
+
+ setup: (inputs) =>
+ { value } = match 'any', inputs
+ super value: Input.value value
- tick: => @out\set tobool @inputs\value!
+ tick: => @out\set tobool @inputs\value!
{
:eq, '==': eq
diff --git a/lib/math.moon b/lib/math.moon
index 84e6682..69d83bb 100644
--- a/lib/math.moon
+++ b/lib/math.moon
@@ -17,36 +17,31 @@ class ReduceOp extends Op
accum = @.fn accum, val
@out\set accum
-class add extends ReduceOp
- @doc: "(+ a b [c]...)
-(add a b [c]...) - add values"
-
- fn: (a, b) -> a + b
-
-class sub extends ReduceOp
- @doc: "(- a b [c]...)
-(sub a b [c]...) - subtract values
-
-subtracts all other arguments from a"
-
- fn: (a, b) -> a - b
-
-class mul extends ReduceOp
- @doc: "(* a b [c]...)
-(mul a b [c]...) - multiply values"
+func_op = (arity, func) ->
+ class extends Op
+ new: => super 'num'
- fn: (a, b) -> a * b
+ setup: (inputs) =>
+ { params } = match '*num', inputs
+ if arity != '*'
+ err = Error 'argument', "need exactly #{arity} arguments"
+ assert #params == arity, err
+ super [Input.value p for p in *params]
-class div extends ReduceOp
- @doc: "(/ a b [c]...)
-(div a b [c]...) - divide values
+ tick: => @out\set func unpack @unwrap_all!
-divides a by all other arguments"
+func_def = (name, args, func, summary) ->
+ _, arity = args\gsub ' ', ' '
- fn: (a, b) -> a / b
+ Value.meta
+ meta:
+ :name
+ :summary
+ examples: { "(#{name} #{args})" }
+ value: func_op arity+1, func
-evenodd_op = (name, remainder) ->
- class k extends Op
+evenodd_op = (remainder) ->
+ class extends Op
new: => super 'bool'
setup: (inputs) =>
@@ -59,57 +54,144 @@ evenodd_op = (name, remainder) ->
{ :val, :div } = @unwrap_all!
@out\set (val % div) == remainder
- k.__name = name
- k.doc = "(#{name} a [div]) - check for #{name} divison
-
-div defaults to 2"
- k
-
-func_op = (name, arity, func) ->
- k = class extends Op
- new: => super 'num'
-
- setup: (inputs) =>
- { params } = match '*num', inputs
- if arity != '*'
- err = Error 'argument', "need exactly #{arity} arguments"
- assert #params == arity, err
- super [Input.value p for p in *params]
-
- tick: => @out\set func unpack @unwrap_all!
-
- k.__name = name
- k
-
-mod = func_op 'mod', 2, (a, b) -> a % b
-
-module = {
+add = Value.meta
+ meta:
+ name: 'add'
+ summary: "Add values."
+ examples: { '(+ a b [c…])', '(add a b [c…])' }
+ description: "Sum all arguments."
+ value: class extends ReduceOp
+ fn: (a, b) -> a + b
+
+sub = Value.meta
+ meta:
+ name: 'sub'
+ summary: "Subtract values."
+ examples: { '(- a b [c…])', '(sub a b [c…])' }
+ description: "Subtract all other arguments from `a`."
+ value: class extends ReduceOp
+ fn: (a, b) -> a - b
+
+mul = Value.meta
+ meta:
+ name: 'mul'
+ summary: "Multiply values."
+ examples: { '(* a b [c…])', '(mul a b [c…])' }
+ value: class extends ReduceOp
+ fn: (a, b) -> a * b
+
+div = Value.meta
+ meta:
+ name: 'div'
+ summary: "Divide values."
+ examples: { '(/ a b [c…])', '(div a b [c…])' }
+ description: "Divide `a` by all other arguments."
+ value: class extends ReduceOp
+ fn: (a, b) -> a / b
+
+pow = Value.meta
+ meta:
+ name: 'pow'
+ summary: "Raise to a power."
+ examples: { '(^ base exp)', '(pow base exp' }
+ description: "Raise `base` to the power `exp`."
+ value: class extends ReduceOp
+ fn: (a, b) -> a ^ b
+
+mod = Value.meta
+ meta:
+ name: 'mod'
+ summary: 'Modulo operator.'
+ examples: { '(% num div)', '(mod num div)' }
+ description: "Calculate remainder of division by `div`."
+ value: func_op 2, (a, b) -> a % b
+
+even = Value.meta
+ meta:
+ name: 'even'
+ summary: 'Check whether val is even.'
+ examples: { '(even val [div])' }
+ description: "`true` if dividing `val` by `div` has remainder zero.
+`div` defaults to 2."
+ value: evenodd_op 0
+
+odd = Value.meta
+ meta:
+ name: 'odd'
+ summary: 'Check whether val is odd.'
+ examples: { '(odd val [div])' }
+ description: "`true` if dividing `val` by `div` has remainder one.
+`div` defaults to 2."
+ value: evenodd_op 1
+
+mix = Value.meta
+ meta:
+ name: 'mix'
+ summary: 'Linearly interpolate.'
+ examples: { '(mix a b i)' }
+ description: "Interpolate between `a` and `b` using `i` in range 0-1."
+ value: func_op 3, (a, b, i) -> i*b + (1-i)*a
+
+min = Value.meta
+ meta:
+ name: 'min'
+ summary: "Find the minimum."
+ examples: { '(min a b [c…])' }
+ description: "Return the lowest of arguments."
+ value: func_op '*', math.min
+
+max = Value.meta
+ meta:
+ name: 'max'
+ summary: "Find the maximum."
+ examples: { '(max a b [c…])' }
+ description: "Return the highest of arguments."
+ value: func_op '*', math.min
+
+cos = func_def 'cos', 'alpha', math.cos, "Cosine function (radians)."
+sin = func_def 'sin', 'alpha', math.sin, "Sine function (radians)."
+tan = func_def 'tan', 'alpha', math.tan, "Tangent function (radians)."
+acos = func_def 'acos', 'cos', math.acos, "Inverse cosine function (radians)."
+asin = func_def 'asin', 'sin', math.asin, "Inverse sine function (radians)."
+atan = func_def 'atan', 'tan', math.atan, "Inverse tangent function (radians)."
+atan2 = func_def 'atan2', 'y x', math.atan2, "Inverse tangent function (two argument version)."
+cosh = func_def 'cosh', 'alpha', math.cosh, "Hyperbolic cosine function (radians)."
+sinh = func_def 'sinh', 'alpha', math.sinh, "Hyperbolic sine function (radians)."
+tanh = func_def 'tanh', 'alpha', math.tanh, "Hyperbolic tangent function (radians)."
+
+floor = func_def 'floor', 'val', math.floor, "Round towards negative infinity."
+ceil = func_def 'ceil', 'val', math.ceil, "Round towards positive infinity."
+abs = func_def 'abs', 'val', math.abs, "Get the absolute value."
+
+exp = func_def 'exp', 'exp', math.floor, "*e* number raised to a power."
+log = func_def 'log', 'val base', math.log, "Logarithm with given base."
+log10 = func_def 'log10', 'val', math.log10, "Logarithm with base 10."
+sqrt = func_def 'sqrt', 'val', math.sqrt, "Square root function."
+
+{
:add, '+': add
:sub, '-': sub
:mul, '*': mul
:div, '/': div
+ :pow, '^': pow
:mod, '%': mod
- mix: func_op 'mix', 3, (a, b, i) -> i*b + (1-i)*a
- even: evenodd_op 'even', 0
- odd: evenodd_op 'odd', 1
+ :even, :odd
- pi: math.pi
- tau: math.pi*2
- huge: math.huge
-}
-
-for name, arity in pairs {
- exp: 1, log: 2, log10: 1, sqrt: 1
+ :mix
+ :min, :max
- cos: 1, sin: 1, tan: 1
- asin: 1, acos: 1, atan: 1, atan2: 2
- sinh: 1, cosh: 1, tanh: 1
+ pi: with Value.wrap math.pi
+ .meta = summary: 'The pi constant.'
+ tau: with Value.wrap math.pi*2
+ .meta = summary: 'The tau constant.'
+ huge: with Value.wrap math.huge
+ .meta = summary: 'Positive infinity constant.'
- min: '*', max: '*'
+ :sin, :cos, :tan
+ :asin, :acos, :atan, :atan2
+ :sinh, :cosh, :tanh
- floor: 1, ceil: 1, abs: 1
+ :floor, :ceil, :abs
+ :exp, :log, :log10, :sqrt
}
- module[name] = func_op name, arity, math[name]
-
-module
diff --git a/lib/midi.moon b/lib/midi.moon
index 33fe936..e978702 100644
--- a/lib/midi.moon
+++ b/lib/midi.moon
@@ -1,63 +1,73 @@
import Value, Op, Input, match from require 'core.base'
import input, output, inout, apply_range from require 'lib.midi.core'
-class gate extends Op
- @doc: "(midi/gate port note [chan]) - gate from note-on and note-off messages"
-
- new: =>
- super 'bool', false
-
- setup: (inputs) =>
- { port, note, chan } = match 'midi/port num num?', inputs
- super
- port: Input.io port
- note: Input.value note
- chan: Input.value chan or Value.num -1
-
- tick: =>
- { :port, :note, :chan } = @inputs
-
- if note\dirty! or chan\dirty!
- @out\set false
-
- if port\dirty!
- for msg in port!\receive!
- if msg.a == note! and (chan! == -1 or msg.chan == chan!)
- if msg.status == 'note-on'
- @out\set true
- elseif msg.status == 'note-off'
- @out\set false
-
-class trig extends Op
- @doc: "(midi/trig port note [chan]) - gate from note-on and note-off messages"
-
- new: =>
- super 'bang', false
-
- setup: (inputs) =>
- { port, note, chan } = match 'midi/port num num?', inputs
- super
- port: Input.io port
- note: Input.value note
- chan: Input.value chan or Value.num -1
-
- tick: =>
- { :port, :note, :chan } = @inputs
-
- if note\dirty! or chan\dirty!
- @out\set false
-
- if port\dirty!
- for msg in port!\receive!
- if msg.a == note! and (chan! == -1 or msg.chan == chan!)
- if msg.status == 'note-on'
- @out\set true
-
-
-class cc extends Op
- @doc: "(midi/cc port cc [chan [range]]) - MIDI CC to number
-
-range can be one of:
+gate = Value.meta
+ meta:
+ name: 'gate'
+ summary: "gate from note-on and note-off messages."
+ examples: { '(midi/gate port note [chan])' }
+
+ value: class extends Op
+ new: =>
+ super 'bool', false
+
+ setup: (inputs) =>
+ { port, note, chan } = match 'midi/port num num?', inputs
+ super
+ port: Input.io port
+ note: Input.value note
+ chan: Input.value chan or Value.num -1
+
+ tick: =>
+ { :port, :note, :chan } = @inputs
+
+ if note\dirty! or chan\dirty!
+ @out\set false
+
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.a == note! and (chan! == -1 or msg.chan == chan!)
+ if msg.status == 'note-on'
+ @out\set true
+ elseif msg.status == 'note-off'
+ @out\set false
+
+trig = Value.meta
+ meta:
+ name: 'trig'
+ summary: "`bang`s from note-on messages."
+ examples: { '(midi/trig port note [chan])' }
+
+ value: class extends Op
+ new: =>
+ super 'bang', false
+
+ setup: (inputs) =>
+ { port, note, chan } = match 'midi/port num num?', inputs
+ super
+ port: Input.io port
+ note: Input.value note
+ chan: Input.value chan or Value.num -1
+
+ tick: =>
+ { :port, :note, :chan } = @inputs
+
+ if note\dirty! or chan\dirty!
+ @out\set false
+
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.a == note! and (chan! == -1 or msg.chan == chan!)
+ if msg.status == 'note-on'
+ @out\set true
+
+trig = Value.meta
+ meta:
+ name: 'trig'
+ summary: "`num` from cc-change messages."
+ examples: { '(midi/cc port cc [chan [range]])' }
+ description: "
+`range` can be one of:
- 'raw' [ 0 - 128[
- 'uni' [ 0 - 1[ (default)
- 'bip' [-1 - 1[
@@ -65,28 +75,29 @@ range can be one of:
- 'deg' [ 0 - 360[
- (num) [ 0 - num["
- new: =>
- super 'num'
-
- setup: (inputs) =>
- { port, cc, chan, range } = match 'midi/port num num? any?', inputs
- super
- port: Input.io port
- cc: Input.value cc
- chan: Input.value chan or Value.num -1
- range: Input.value range or Value.str 'uni'
-
- if not @out\unwrap!
- @out\set apply_range @inputs.range, 0
-
- tick: =>
- { :port, :cc, :chan, :range } = @inputs
- if port\dirty!
- for msg in port!\receive!
- if msg.status == 'control-change' and
- (chan! == -1 or msg.chan == chan!) and
- msg.a == cc!
- @out\set apply_range range, msg.b
+ value: class extends Op
+ new: =>
+ super 'num'
+
+ setup: (inputs) =>
+ { port, cc, chan, range } = match 'midi/port num num? any?', inputs
+ super
+ port: Input.io port
+ cc: Input.value cc
+ chan: Input.value chan or Value.num -1
+ range: Input.value range or Value.str 'uni'
+
+ if not @out\unwrap!
+ @out\set apply_range @inputs.range, 0
+
+ tick: =>
+ { :port, :cc, :chan, :range } = @inputs
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.status == 'control-change' and
+ (chan! == -1 or msg.chan == chan!) and
+ msg.a == cc!
+ @out\set apply_range range, msg.b
{
:input
diff --git a/lib/midi/core.moon b/lib/midi/core.moon
index c437259..4986811 100644
--- a/lib/midi/core.moon
+++ b/lib/midi/core.moon
@@ -1,4 +1,4 @@
-import IO, Op, Registry, Input, Error, match from require 'core.base'
+import Value, IO, Op, Registry, Input, Error, match from require 'core.base'
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
bit = do
@@ -67,34 +67,46 @@ class PortOp extends Op
out = out and find_port RtMidiOut, out!
@out\set MidiPort inp, out
-class input extends PortOp
- @doc: "(midi/input name) - create a MIDI input port"
-
- setup: (inputs) =>
- { name } = match 'str', inputs
- super name: Input.value name
-
- tick: => super @inputs.name
-
-class output extends PortOp
- @doc: "(midi/output name) - create a MIDI output port"
-
- setup: (inputs) =>
- { name } = match 'str', inputs
- super name: Input.value name
-
- tick: => super nil, @inputs.name
-
-class inout extends PortOp
- @doc: "(midi/inout inname outname) - create a bidirectional MIDI port"
-
- setup: (inputs) =>
- { inp, out } = match 'str str', inputs
- super
- inp: Input.value inp
- out: Input.value out
-
- tick: => super @inputs.inp, @inputs.out
+input = Value.meta
+ meta:
+ name: 'input'
+ summary: "Create a MIDI input port."
+ examples: { '(midi/input name)' }
+
+ value: class extends PortOp
+ setup: (inputs) =>
+ { name } = match 'str', inputs
+ super name: Input.value name
+
+ tick: => super @inputs.name
+
+output = Value.meta
+ meta:
+ name: 'output'
+ summary: "Create a MIDI output port."
+ examples: { '(midi/output name)' }
+
+ value: class extends PortOp
+ setup: (inputs) =>
+ { name } = match 'str', inputs
+ super name: Input.value name
+
+ tick: => super nil, @inputs.name
+
+inout = Value.meta
+ meta:
+ name: 'inout'
+ summary: "Create a bidirectional MIDI port."
+ examples: { '(midi/inout name)' }
+
+ value: class extends PortOp
+ setup: (inputs) =>
+ { inp, out } = match 'str str', inputs
+ super
+ inp: Input.value inp
+ out: Input.value out
+
+ tick: => super @inputs.inp, @inputs.out
apply_range = (range, val) ->
if range\type! == 'str'
diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon
index 1084f66..37ccb82 100644
--- a/lib/midi/launchctl.moon
+++ b/lib/midi/launchctl.moon
@@ -5,9 +5,12 @@ import bor, lshift from bit
unpack or= table.unpack
color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4)
-class cc_seq extends Op
- @doc: "(launctl/cc-seq port i start chan [steps [range]]) - CC-Sequencer
-
+cc_seq = Value.meta
+ meta:
+ name: 'cc-seq'
+ summary: "MIDI CC-Sequencer."
+ examples: { '(launchctl/cc-seq port i start chan [steps [range]])' }
+ description: "
returns the value for the i-th step steps (buttons starting from start).
steps defaults to 8.
@@ -19,104 +22,109 @@ range can be one of:
- 'deg' [ 0 - 360[
- (num) [ 0 - num["
- new: => super 'num'
-
- setup: (inputs) =>
- { port, i, start,
- chan, steps, range } = match 'midi/port num num num num? any?', inputs
-
- super
- port: Input.io port
- i: Input.value i
- start: Input.value start
- chan: Input.value chan
- steps: Input.value steps or Value.num 8
- range: Input.value range or Value.str 'uni'
-
- if not @out\unwrap!
- @out\set apply_range @inputs.range, 0
-
- tick: =>
- { :port, :i, :start, :chan, :steps, :range } = @inputs
-
- if steps\dirty!
- while steps! > #@state
- table.insert @state, 0
- while steps! < #@state
- table.remove @state
-
- curr_i = i! % #@state
- if port\dirty!
- changed = false
- for msg in port!\receive!
- if msg.status == 'control-change' and msg.chan == chan!
- rel_i = msg.a - start!
- if rel_i >= 0 and rel_i < #@state
- @state[rel_i+1] = msg.b
- changed = rel_i == curr_i
- @out\set apply_range range, @state[curr_i+1] if changed
- else
- @out\set apply_range range, @state[curr_i+1]
-
-class gate_seq extends Op
- @doc: "(launctl/gate-seq port i start chan [steps]) - Gate-Sequencer
-
-returns true or false for the i-th step steps (buttons starting from start).
-steps defaults to 8."
-
- new: =>
- super 'bool', false
- @state = {}
-
- setup: (inputs) =>
- { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs
-
- super
- port: Input.io port
- i: Input.value i
- start: Input.value start
- chan: Input.value chan
- steps: Input.value steps or Value.num 8
-
- light = (set, active) ->
- set = if set then 'S' else ' '
- active = if active then 'A' else ' '
- color switch set .. active
- when ' ' then 0, 0
- when ' A' then 1, 1
- when 'S ' then 1, 0
- when 'SA' then 3, 1
-
- display: (i, active) =>
- { :port, :start, :chan } = @unwrap_all!
- port\send 'note-on', chan, (start + i), light @state[i+1], active
-
- tick: =>
- { :port, :i, :start, :chan, :steps } = @inputs
-
- if steps\dirty!
- while steps! > #@state
- table.insert @state, false
- while steps! < #@state
- table.remove @state
-
- curr_i = i! % #@state
-
- if port\dirty!
- for msg in port!\receive!
- if msg.status == 'note-on' and msg.chan == chan!
- rel_i = msg.a - start!
- if rel_i >= 0 and rel_i < #@state
- @state[rel_i+1] = not @state[rel_i+1]
- @display rel_i, rel_i == curr_i
-
- if i\dirty!
- prev_i = (curr_i - 1) % #@state
-
- @display curr_i, true
- @display prev_i, false
-
- @out\set @state[curr_i+1]
+ value: class extends Op
+ new: => super 'num'
+
+ setup: (inputs) =>
+ { port, i, start,
+ chan, steps, range } = match 'midi/port num num num num? any?', inputs
+
+ super
+ port: Input.io port
+ i: Input.value i
+ start: Input.value start
+ chan: Input.value chan
+ steps: Input.value steps or Value.num 8
+ range: Input.value range or Value.str 'uni'
+
+ if not @out\unwrap!
+ @out\set apply_range @inputs.range, 0
+
+ tick: =>
+ { :port, :i, :start, :chan, :steps, :range } = @inputs
+
+ if steps\dirty!
+ while steps! > #@state
+ table.insert @state, 0
+ while steps! < #@state
+ table.remove @state
+
+ curr_i = i! % #@state
+ if port\dirty!
+ changed = false
+ for msg in port!\receive!
+ if msg.status == 'control-change' and msg.chan == chan!
+ rel_i = msg.a - start!
+ if rel_i >= 0 and rel_i < #@state
+ @state[rel_i+1] = msg.b
+ changed = rel_i == curr_i
+ @out\set apply_range range, @state[curr_i+1] if changed
+ else
+ @out\set apply_range range, @state[curr_i+1]
+
+gate_seq = Value.meta
+ meta:
+ name: 'gate-seq'
+ summary: "MIDI Gate-Sequencer."
+ examples: { '(launchctl/gate-seq port i start chan [steps])' }
+ description: "
+returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from
+`start`). `steps` defaults to 8."
+
+ value: class extends Op
+ new: =>
+ super 'bool', false
+ @state = {}
+
+ setup: (inputs) =>
+ { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs
+
+ super
+ port: Input.io port
+ i: Input.value i
+ start: Input.value start
+ chan: Input.value chan
+ steps: Input.value steps or Value.num 8
+
+ light = (set, active) ->
+ set = if set then 'S' else ' '
+ active = if active then 'A' else ' '
+ color switch set .. active
+ when ' ' then 0, 0
+ when ' A' then 1, 1
+ when 'S ' then 1, 0
+ when 'SA' then 3, 1
+
+ display: (i, active) =>
+ { :port, :start, :chan } = @unwrap_all!
+ port\send 'note-on', chan, (start + i), light @state[i+1], active
+
+ tick: =>
+ { :port, :i, :start, :chan, :steps } = @inputs
+
+ if steps\dirty!
+ while steps! > #@state
+ table.insert @state, false
+ while steps! < #@state
+ table.remove @state
+
+ curr_i = i! % #@state
+
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.status == 'note-on' and msg.chan == chan!
+ rel_i = msg.a - start!
+ if rel_i >= 0 and rel_i < #@state
+ @state[rel_i+1] = not @state[rel_i+1]
+ @display rel_i, rel_i == curr_i
+
+ if i\dirty!
+ prev_i = (curr_i - 1) % #@state
+
+ @display curr_i, true
+ @display prev_i, false
+
+ @out\set @state[curr_i+1]
{
'gate-seq': gate_seq
diff --git a/lib/osc.moon b/lib/osc.moon
index 101fb11..fe4eae4 100644
--- a/lib/osc.moon
+++ b/lib/osc.moon
@@ -1,64 +1,74 @@
-import Op, Input, match from require 'core.base'
+import Op, Value, Input, match from require 'core.base'
import pack from require 'osc'
import dns, udp from require 'socket'
unpack or= table.unpack
-class connect extends Op
- @doc: "(osc/connect host port) - UDP remote definition"
+connect = Value.meta
+ meta:
+ name: 'connect'
+ summary: "Create a UDP remote."
+ examples: { '(osc/connect host port)' }
- new: => super 'udp/socket'
+ value: class extends Op
+ new: => super 'udp/socket'
- setup: (inputs) =>
- { host, port } = match 'str num', inputs
- super
- host: Input.value host
- port: Input.value port
+ setup: (inputs) =>
+ { host, port } = match 'str num', inputs
+ super
+ host: Input.value host
+ port: Input.value port
- tick: =>
- { :host, :port } = @unwrap_all!
- ip = dns.toip host
+ tick: =>
+ { :host, :port } = @unwrap_all!
+ ip = dns.toip host
- @out\set with sock = udp!
- \setpeername ip, port
+ @out\set with sock = udp!
+ \setpeername ip, port
-class send extends Op
- @doc: "(osc/send socket path val) - send a value via OSC
+send = Value.meta
+ meta:
+ name: 'send'
+ summary: "Send a value via OSC."
+ examples: { '(osc/send socket path val)' }
+ description: "sends a message only when `val` is dirty."
-sends a message only when val is dirty."
+ value: class extends Op
+ setup: (inputs) =>
+ { socket, path, value } = match 'udp/socket str any', inputs
+ super
+ socket: Input.cold socket
+ path: Input.cold path
+ value: Input.value value
- setup: (inputs) =>
- { socket, path, value } = match 'udp/socket str any', inputs
- super
- socket: Input.cold socket
- path: Input.cold path
- value: Input.value value
+ tick: =>
+ if @inputs.value\dirty!
+ { :socket, :path, :value } = @unwrap_all!
+ msg = if 'table' == type value
+ pack path, unpack value
+ else
+ pack path, value
+ socket\send msg
- tick: =>
- if @inputs.value\dirty!
- { :socket, :path, :value } = @unwrap_all!
- msg = if 'table' == type value
- pack path, unpack value
- else
- pack path, value
- socket\send msg
-
-class send_state extends Op
- @doc: "(osc/send! socket path val) - synchronize a value via OSC
+send_state = Value.meta
+ meta:
+ name: 'send'
+ summary: "Synchronize a value via OSC."
+ examples: { '(osc/send! socket path val)' }
+ description: "sends a message whenever any parameter is dirty."
-sends a whenever any parameter changes."
+ value: class extends Op
+ setup: (inputs) =>
+ { socket, path, value } = match 'udp/socket str any', inputs
+ super
+ socket: Input.value socket
+ path: Input.value path
+ value: Input.value value
- setup: (inputs) =>
- { socket, path, value } = match 'udp/socket str any', inputs
- super
- socket: Input.value socket
- path: Input.value path
- value: Input.value value
-
- tick: =>
- { :socket, :path, :value } = @unwrap_all!
- msg = pack path, value
- socket\send msg
+ tick: =>
+ { :socket, :path, :value } = @unwrap_all!
+ msg = pack path, value
+ socket\send msg
{
:connect
diff --git a/lib/pilot.moon b/lib/pilot.moon
index a687e48..b851440 100644
--- a/lib/pilot.moon
+++ b/lib/pilot.moon
@@ -1,4 +1,4 @@
-import Op, Input, Error, match from require 'core.base'
+import Op, Value, Input, Error, match from require 'core.base'
import udp from require 'socket'
local conn
@@ -20,53 +20,65 @@ send = (...) ->
conn or= udp!
conn\sendto str, '127.0.0.1', 49161
-class play extends Op
- @doc: "(play trig ch oct note [vel [len]]) - play a note when trig is live"
+play = Value.meta
+ meta:
+ name: 'play'
+ summary: "Play a note when a bang arrives."
+ examples: { '(pilot/play trig ch oct note [vel [len]])' }
- setup: (inputs) =>
- { trig, args } = match 'bang *any', inputs
- assert #args < 6, Error 'argument', "too many arguments!"
- super
- trig: Input.event trig
- args: [Input.cold a for a in *args]
+ value: class extends Op
+ setup: (inputs) =>
+ { trig, args } = match 'bang *any', inputs
+ assert #args < 6, Error 'argument', "too many arguments!"
+ super
+ trig: Input.event trig
+ args: [Input.cold a for a in *args]
- tick: =>
- { :trig, :args } = @inputs
- if trig\dirty! and trig!
- send [a! for a in *@inputs.args]
+ tick: =>
+ { :trig, :args } = @inputs
+ if trig\dirty! and trig!
+ send [a! for a in *@inputs.args]
-class play_ extends Op
- @doc: "(play! ch oct note [vel [len]]) - play a note when note is live"
+play_ = Value.meta
+ meta:
+ name: 'play!'
+ summary: "Play a note when a note arrives."
+ examples: { '(pilot/play! ch oct note [vel [len]])' }
- setup: (inputs) =>
- { chan, octv, note, args } = match 'any any any *any', inputs
- assert #args < 3, Error 'argument', "too many arguments!"
- super
- chan: Input.cold chan
- octv: Input.cold octv
- note: Input.event note
- args: [Input.cold a for a in *args]
+ value: class extends Op
+ setup: (inputs) =>
+ { chan, octv, note, args } = match 'any any any *any', inputs
+ assert #args < 3, Error 'argument', "too many arguments!"
+ super
+ chan: Input.cold chan
+ octv: Input.cold octv
+ note: Input.event note
+ args: [Input.cold a for a in *args]
- tick: =>
- if @inputs.note\dirty!
- { :chan, :oct, :note, :args } = @unwrap_all!
- send { chan, oct, note }, args
+ tick: =>
+ if @inputs.note\dirty!
+ { :chan, :oct, :note, :args } = @unwrap_all!
+ send { chan, oct, note }, args
-class effect extends Op
- @doc: "(effect which a b) - set an effect
+effect = Value.meta
+ meta:
+ name: 'effect'
+ summary: "Set effect parameters."
+ examples: { '(pilot/effect which a b)' }
+ description: "`effect` should be one of 'DIS', 'CHO', 'REV' or 'FEE'"
-which is one of 'DIS', 'CHO', 'REV' or 'FEE'"
+ value: class extends Op
+ setup: (inputs) =>
+ { which, a, b } = match 'str num num', inputs
+ super {
+ Input.cold which
+ Input.value a
+ Input.value b
+ }
- setup: (inputs) =>
- { which, a, b } = match 'str num num', inputs
- super {
- Input.cold which
- Input.value a
- Input.value b
- }
+ tick: =>
+ send @unwrap_all!
- tick: =>
- send @unwrap_all!
{
:play
'play!': play_
diff --git a/lib/random.moon b/lib/random.moon
index b97671d..a3124fa 100644
--- a/lib/random.moon
+++ b/lib/random.moon
@@ -23,38 +23,20 @@ range can be one of:
- (num) [ 0 - num["
class num extends Op
- @doc: "(random/num [trigger] [range]) - create a random number
-
-generates a random value in range on create and trigger.
+num = Value.meta
+ meta:
+ name: 'num'
+ summary: 'Generate a random number.'
+ examples: { '(random/num [trigger] [range]))' }
+ description: "generate a random value in `range` when created and on `trig`.
#{range_doc}"
- new: =>
- super 'num'
- @gen!
-
- gen: => @state = { math.random! }
-
- setup: (inputs) =>
- { trig, range } = match 'bang? any?', inputs
- super
- trig: trig and Input.event trig
- range: Input.value range or Value.str 'uni'
-
- tick: =>
- @gen! if @inputs.trig and @inputs.trig\dirty!
- @out\set apply_range @inputs.range, @state[1]
-vec_ = (n) ->
- class vec extends Op
- @doc: "(random/vec#{n} [trigger] [range]) - create a random number
-
-generates a random vec#{n} on create and trigger.
-each component is in range.
-#{range_doc}"
+ value: class extends Op
new: =>
- super "vec#{n}"
+ super 'num'
@gen!
- gen: => @state = for i=1,n do math.random!
+ gen: => @state = { math.random! }
setup: (inputs) =>
{ trig, range } = match 'bang? any?', inputs
@@ -64,10 +46,33 @@ each component is in range.
tick: =>
@gen! if @inputs.trig and @inputs.trig\dirty!
- @out\set [apply_range @inputs.range, v for v in *@state]
+ @out\set apply_range @inputs.range, @state[1]
+
+vec_ = (n) ->
+ Value.meta
+ meta:
+ name: "vec#{n}"
+ summary: 'Generate a random vector.'
+ examples: { '(random/vec#{n} [trigger] [range]))' }
+ description: "generate a random vec#{n} in `range` when created and on `trig`.
+#{range_doc}"
+
+ value: class extends Op
+ new: =>
+ super "vec#{n}"
+ @gen!
+
+ gen: => @state = for i=1,n do math.random!
+
+ setup: (inputs) =>
+ { trig, range } = match 'bang? any?', inputs
+ super
+ trig: trig and Input.event trig
+ range: Input.value range or Value.str 'uni'
- vec.__name = "vec#{n}"
- vec
+ tick: =>
+ @gen! if @inputs.trig and @inputs.trig\dirty!
+ @out\set [apply_range @inputs.range, v for v in *@state]
{
:num
diff --git a/lib/sc.moon b/lib/sc.moon
index 7562620..dd9e826 100644
--- a/lib/sc.moon
+++ b/lib/sc.moon
@@ -1,30 +1,37 @@
-import Op, Input, Error, match from require 'core.base'
+import Op, Value, Input, Error, match from require 'core.base'
import pack from require 'osc'
import dns, udp from require 'socket'
-class play extends Op
- @doc: "(sc/play socket synth trigger [name-str val]...) - play a SC SynthDef"
+play = Value.meta
+ meta:
+ name: 'play'
+ summary: 'Play a SuperCollider SynthDef.'
+ examples: { '(play socket synth trig [param val…])' }
+ description: "
+Plays the synth `synth` on the `udp/socket` `socket` whenever `trig` is live.
+Any number of parameter-value pairs can be specified and are captured and sent
+together with the note when triggered."
+ value: class extends Op
+ setup: (inputs) =>
+ { socket, synth, trig, ctrls } = match 'udp/socket str bang *any?', inputs
- setup: (inputs) =>
- { socket, synth, trig, ctrls } = match 'udp/socket str bang *any?', inputs
+ assert #ctrls % 2 == 0, Error 'argument', "parameters need to be specified as pairs"
+ for key in *ctrls[1,,2]
+ assert key\type! == 'str', Error 'argument', "ony strings are supported as control names"
+ for val in *ctrls[2,,2]
+ assert val\type! == 'num', Error 'argument', "only numbers are supported as control values"
- assert #ctrls % 2 == 0, Error 'argument', "parameters need to be specified as pairs"
- for key in *ctrls[1,,2]
- assert key\type! == 'str', Error 'argument', "ony strings are supported as control names"
- for val in *ctrls[2,,2]
- assert val\type! == 'num', Error 'argument', "only numbers are supported as control values"
+ super
+ trig: Input.event trig
+ socket: Input.cold socket
+ synth: Input.cold synth
+ ctrls: [Input.cold v for v in *ctrls]
- super
- trig: Input.event trig
- socket: Input.cold socket
- synth: Input.cold synth
- ctrls: [Input.cold v for v in *ctrls]
-
- tick: =>
- if @inputs.trig\dirty! and @inputs.trig!
- { :socket, :synth, :ctrls } = @unwrap_all!
- msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls
- socket\send msg
+ tick: =>
+ if @inputs.trig\dirty! and @inputs.trig!
+ { :socket, :synth, :ctrls } = @unwrap_all!
+ msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls
+ socket\send msg
{
:play
diff --git a/lib/string.moon b/lib/string.moon
index 06509f3..8b48977 100644
--- a/lib/string.moon
+++ b/lib/string.moon
@@ -1,12 +1,15 @@
-import Op, Input from require 'core.base'
+import Op, Value, Input from require 'core.base'
-class str extends Op
- @doc: "(str v1 [v2]...)
-(.. v1 [v2]...) - concatenate/stringify values"
- new: => super 'str'
+str = Value.meta
+ meta:
+ name: 'str'
+ summary: "Concatenate/stringify values."
+ examples: { '(.. v1 [v2…])', '(str v1 [v2…])' }
+ value: class extends Op
+ new: => super 'str'
- setup: (inputs) => super [Input.value v for v in *inputs]
- tick: => @out\set table.concat [tostring v! for v in *@inputs]
+ setup: (inputs) => super [Input.value v for v in *inputs]
+ tick: => @out\set table.concat [tostring v! for v in *@inputs]
{
:str, '..': str
diff --git a/lib/time.moon b/lib/time.moon
index 8e9aded..182cf4d 100644
--- a/lib/time.moon
+++ b/lib/time.moon
@@ -1,5 +1,4 @@
-import Registry, Value, IO, Op, Input, match
- from require 'core.base'
+import Registry, Value, IO, Op, Input, match from require 'core.base'
import monotime from require 'system'
class Clock extends IO
@@ -20,131 +19,148 @@ class Clock extends IO
dirty: => @is_dirty
-class clock extends Op
- @doc: "(clock [fps]) - a clock source
-
+clock = Value.meta
+ meta:
+ name: 'clock'
+ summary: "Create a clock source."
+ examples: { '(clock)', '(clock fps)' }
+ description: "
IO that triggers other operators at a fixed frame rate.
-fps defaults to 60 and has to be an eval-time constant"
-
- new: => super 'clock'
-
- setup: (inputs) =>
- { fps } = match 'num?', inputs
- super fps: Input.value fps or Value.num 60
-
- tick: =>
- if @inputs.fps\dirty!
- @out\set Clock 1 / @inputs.fps!
-
-class lfo extends Op
- @doc: "(lfo [clock] freq [wave]) - low-frequency oscillator
-
+`fps` defaults to 60 and has to be an eval-time constant"
+ value: class extends Op
+ new: => super 'clock'
+
+ setup: (inputs) =>
+ { fps } = match 'num?', inputs
+ super fps: Input.value fps or Value.num 60
+
+ tick: =>
+ if @inputs.fps\dirty!
+ @out\set Clock 1 / @inputs.fps!
+
+lfo = Value.meta
+ meta:
+ name: 'lfo'
+ summary: "Low-frequency oscillator."
+ examples: { '(lfo [clock] freq wave)' }
+ description: "
oscillates between 0 and 1 at the frequency freq.
wave selects the wave shape from the following:
-- sin (default)
-- saw
-- tri"
-
- new: =>
- super 'num'
- @state.phase or= 0
-
- default_wave = Value.str 'sin'
- setup: (inputs, scope) =>
- { clock, freq, wave } = match 'clock? num any?', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- freq: Input.value freq
- wave: Input.value wave or default_wave
-
- tau = math.pi * 2
- tick: =>
- if @inputs.clock\dirty!
- { :clock, :freq, :wave } = @unwrap_all!
-
- @state.phase += clock.dt * freq
- @out\set switch wave
- when 'sin' then .5 + .5 * math.cos @state.phase * tau
- when 'saw' then @state.phase % 1
- when 'tri' then math.abs (2*@state.phase % 2) - 1
- else error "unknown wave type"
-
-class ramp extends Op
- @doc: "(ramp [clock] period [max]) - sawtooth lfo
-
+- `'sin'` (default)
+- `'saw'`
+- `'tri'`"
+ value: class extends Op
+ new: =>
+ super 'num'
+ @state.phase or= 0
+
+ default_wave = Value.str 'sin'
+ setup: (inputs, scope) =>
+ { clock, freq, wave } = match 'clock? num any?', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ freq: Input.value freq
+ wave: Input.value wave or default_wave
+
+ tau = math.pi * 2
+ tick: =>
+ if @inputs.clock\dirty!
+ { :clock, :freq, :wave } = @unwrap_all!
+
+ @state.phase += clock.dt * freq
+ @out\set switch wave
+ when 'sin' then .5 + .5 * math.cos @state.phase * tau
+ when 'saw' then @state.phase % 1
+ when 'tri' then math.abs (2*@state.phase % 2) - 1
+ else error "unknown wave type"
+
+ramp = Value.meta
+ meta:
+ name: 'ramp'
+ summary: "Sawtooth LFO."
+ examples: { '(ramp [clock] period [max])' }
+ description: "
ramps from 0 to max (default same as ramp) once every period seconds."
-
- new: =>
- super 'num'
- @state.phase or= 0
-
- setup: (inputs, scope) =>
- { clock, period, max } = match 'clock? num num?', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- period: Input.value period
- max: max and Input.value max
-
- tick: =>
- clock_dirty = @inputs.clock\dirty!
- if clock_dirty
- { :clock, :period, :max } = @unwrap_all!
- max or= period
- @state.phase += clock.dt / period
-
- while @state.phase >= 1
- @state.phase -= 1
-
- if clock_dirty or (@inputs.max and @inputs.max\dirty!)
- @out\set @state.phase * max
-
-class tick extends Op
- @doc: "(tick [clock] period) - count ticks
-
-counts upwards by one every period seconds and returns the number of completed ticks."
- new: =>
- super 'num', 0
- @state.phase or= 0
- @state.count or= 0
-
- setup: (inputs, scope) =>
- { clock, period } = match 'clock? num', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- period: Input.value period
-
- tick: =>
- if @inputs.clock\dirty!
- { :clock, :period, :max } = @unwrap_all!
- @state.phase += clock.dt / period
-
- while @state.phase >= 1
- @state.phase -= 1
- @state.count += 1
- @out\set @state.count
-
-class every extends Op
- @doc: "(every [clock] period) - trigger every period seconds
-
-returns true once every period seconds."
- new: =>
- super 'bang'
- @state.phase or= 0
-
- setup: (inputs, scope) =>
- { clock, period } = match 'clock? num', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- period: Input.value period
-
- tick: =>
- if @inputs.clock\dirty!
- { :clock, :period, :max } = @unwrap_all!
- @state.phase += clock.dt / period
-
- while @state.phase >= 1
- @state.phase -= 1
- @out\set true
+ value: class extends Op
+ new: =>
+ super 'num'
+ @state.phase or= 0
+
+ setup: (inputs, scope) =>
+ { clock, period, max } = match 'clock? num num?', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ period: Input.value period
+ max: max and Input.value max
+
+ tick: =>
+ clock_dirty = @inputs.clock\dirty!
+ if clock_dirty
+ { :clock, :period, :max } = @unwrap_all!
+ max or= period
+ @state.phase += clock.dt / period
+
+ while @state.phase >= 1
+ @state.phase -= 1
+
+ if clock_dirty or (@inputs.max and @inputs.max\dirty!)
+ @out\set @state.phase * max
+
+tick = Value.meta
+ meta:
+ name: 'tick'
+ summary: "Count ticks."
+ examples: { '(tick [clock] period)' }
+ description: "
+counts upwards by one every period seconds and returns the number of completed
+ticks."
+ value: class extends Op
+ new: =>
+ super 'num', 0
+ @state.phase or= 0
+ @state.count or= 0
+
+ setup: (inputs, scope) =>
+ { clock, period } = match 'clock? num', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ period: Input.value period
+
+ tick: =>
+ if @inputs.clock\dirty!
+ { :clock, :period, :max } = @unwrap_all!
+ @state.phase += clock.dt / period
+
+ while @state.phase >= 1
+ @state.phase -= 1
+ @state.count += 1
+ @out\set @state.count
+
+every = Value.meta
+ meta:
+ name: 'every'
+ summary: "Emit bangs."
+ examples: { '(every [clock] period)' }
+ description: "returns true once every period seconds."
+ value: class extends Op
+ new: =>
+ super 'bang'
+ @state.phase or= 0
+
+ setup: (inputs, scope) =>
+ { clock, period } = match 'clock? num', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ period: Input.value period
+
+ tick: =>
+ if @inputs.clock\dirty!
+ { :clock, :period, :max } = @unwrap_all!
+ @state.phase += clock.dt / period
+
+ while @state.phase >= 1
+ @state.phase -= 1
+ @out\set true
{
:clock
@@ -152,5 +168,8 @@ returns true once every period seconds."
:ramp
:tick
:every
- '*clock*': Value 'clock', Clock 1/60
+ '*clock*': with Value 'clock', Clock 1/60
+ .meta =
+ name: '*clock*'
+ summary: 'Default clock source (60fps).'
}
diff --git a/lib/util.moon b/lib/util.moon
index f816eee..a2c65ba 100644
--- a/lib/util.moon
+++ b/lib/util.moon
@@ -7,106 +7,100 @@ all_same = (list) ->
list[1]
-class switch_ extends Op
- @doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs
-
-when i is true, the first value is reproduced.
-when i is false, the second value is reproduced.
-when i is a num, it is (floor)ed and the matching argument (starting from 0) is reproduced."
-
- setup: (inputs) =>
- { i, values } = match 'any *any', inputs
-
- i_type = i\type!
- assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
- typ = all_same [v\type! for v in *values]
- @out = Value typ if not @out or typ != @out.type
-
- super
- i: Input.value i
- values: [Input.value v for v in *values]
-
- tick: =>
- { :i, :values } = @inputs
- active = switch i!
- when true
- values[1]
- when false
- values[2]
- else
- i = 1 + (math.floor i!) % #values
- values[i]
- @out\set active and active!
-
-class route extends Op
- @doc: "(route i v0 [v1 v2...]) - route between multiple inputs
-
-when i is true, the first value is reproduced.
-when i is false, the second value is reproduced.
-when i is a num, it is (floor)ed and the matching argument (starting from 0) is reproduced."
-
- setup: (inputs) =>
- { i, values } = match 'any *any', inputs
-
- i_type = i\type!
- assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
- typ = all_same [v\type! for v in *values]
- @out = Value typ if not @out or typ != @out.type
-
- super
- i: Input.value i
- values: [Input.value v for v in *values]
-
- tick: =>
- { :i, :values } = @inputs
- active = switch i!
- when true
- values[1]
- when false
- values[2]
- else
- i = 1 + (math.floor i!) % #values
- values[i]
- if active and active\dirty!
- @out\set active!
-
-class edge extends Op
- @doc: "(edge bool) - convert rising edges to bangs"
- new: => super 'bang'
-
- setup: (inputs) =>
- { value } = match 'bool', inputs
- super value: Input.value value
-
- tick: =>
- now = @inputs.value!
- if now and not @state.last
- @out\set true
- @state.last = now
-
-class default extends Op
- @doc: "(default stream default) - provide a default value for an event stream
-
-starts out as default but forwards events from stream.
-default defaults to zero."
-
- setup: (params) =>
- { value, init } = match 'any any', inputs
- super
- value: Input.event value
- init: Input.cold init
-
- @out = Value value\type!
- @out\set @inputs.init\unwrap!
-
- tick: =>
- { :value } = @inputs
- if value\dirty!
- @out\set value!
+switch_ = Value.meta
+ meta:
+ name: 'switch'
+ summary: "Switch between multiple inputs."
+ examples: { '(switch i v0 [v1 v2…])' }
+ description: "
+- when `i` is `true`, the first value is reproduced.
+- when `i` is `false`, the second value is reproduced.
+- when `i` is a `num`, it is [math/floor][]ed and the matching argument
+ (indexed starting from 0) is reproduced."
+
+ value: class extends Op
+ setup: (inputs) =>
+ { i, values } = match 'any *any', inputs
+
+ i_type = i\type!
+ assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
+ typ = all_same [v\type! for v in *values]
+ @out = Value typ if not @out or typ != @out.type
+
+ super
+ i: Input.value i
+ values: [Input.value v for v in *values]
+
+ tick: =>
+ { :i, :values } = @inputs
+ active = switch i!
+ when true
+ values[1]
+ when false
+ values[2]
+ else
+ i = 1 + (math.floor i!) % #values
+ values[i]
+ @out\set active and active!
+
+route = Value.meta
+ meta:
+ name: 'route'
+ summary: "Route between multiple inputs."
+ examples: { '(route i v0 [e1 e2…])' }
+ description: "
+- when `i` is `true`, the first event stream is reproduced.
+- when `i` is `false`, the second event stream is reproduced.
+- when `i` is a `num`, it is [math/floor][]ed and the matching argument
+ (indexed starting from 0) is reproduced."
+
+ value: class extends Op
+ setup: (inputs) =>
+ { i, values } = match 'any *any', inputs
+
+ i_type = i\type!
+ assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
+ typ = all_same [v\type! for v in *values]
+ @out = Value typ if not @out or typ != @out.type
+
+ super
+ i: Input.value i
+ values: [Input.value v for v in *values]
+
+ tick: =>
+ { :i, :values } = @inputs
+ active = switch i!
+ when true
+ values[1]
+ when false
+ values[2]
+ else
+ i = 1 + (math.floor i!) % #values
+ values[i]
+ if active and active\dirty!
+ @out\set active!
+
+route = Value.meta
+ meta:
+ name: 'edge'
+ summary: "Convert rising edges to bangs."
+ examples: { '(edge bool)' }
+
+ value: class extends Op
+ new: => super 'bang'
+
+ setup: (inputs) =>
+ { value } = match 'bool', inputs
+ super value: Input.value value
+
+ tick: =>
+ now = @inputs.value!
+ if now and not @state.last
+ @out\set true
+ @state.last = now
{
'switch': switch_
:route
:edge
- :default
}