diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/logic.moon | 159 | ||||
| -rw-r--r-- | lib/math.moon | 218 | ||||
| -rw-r--r-- | lib/midi.moon | 169 | ||||
| -rw-r--r-- | lib/midi/core.moon | 70 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 210 | ||||
| -rw-r--r-- | lib/osc.moon | 102 | ||||
| -rw-r--r-- | lib/pilot.moon | 90 | ||||
| -rw-r--r-- | lib/random.moon | 65 | ||||
| -rw-r--r-- | lib/sc.moon | 49 | ||||
| -rw-r--r-- | lib/string.moon | 17 | ||||
| -rw-r--r-- | lib/time.moon | 267 | ||||
| -rw-r--r-- | lib/util.moon | 188 |
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 } |
