From 5faab5d81cc5ed36824a52a273e83eb20240cb2e Mon Sep 17 00:00:00 2001 From: s-ol Date: Tue, 14 Apr 2020 11:21:00 +0200 Subject: move lib to alv-lib module --- Makefile | 8 +- alv-lib/logic.moon | 153 ++++++++++++++++++++++++ alv-lib/math.moon | 192 +++++++++++++++++++++++++++++ alv-lib/midi.moon | 99 +++++++++++++++ alv-lib/midi/core.moon | 122 +++++++++++++++++++ alv-lib/midi/launchctl.moon | 194 ++++++++++++++++++++++++++++++ alv-lib/osc.moon | 87 ++++++++++++++ alv-lib/pilot.moon | 90 ++++++++++++++ alv-lib/random.moon | 85 +++++++++++++ alv-lib/sc.moon | 83 +++++++++++++ alv-lib/string.moon | 18 +++ alv-lib/time.moon | 285 ++++++++++++++++++++++++++++++++++++++++++++ alv-lib/util.moon | 112 +++++++++++++++++ alv/builtin.moon | 10 +- alv/config.ld | 4 +- alv/extensions.md | 6 +- extra/docs.moon | 2 +- lib/logic.moon | 153 ------------------------ lib/math.moon | 192 ----------------------------- lib/midi.moon | 99 --------------- lib/midi/core.moon | 122 ------------------- lib/midi/launchctl.moon | 194 ------------------------------ lib/osc.moon | 87 -------------- lib/pilot.moon | 90 -------------- lib/random.moon | 85 ------------- lib/sc.moon | 83 ------------- lib/string.moon | 18 --- lib/time.moon | 285 -------------------------------------------- lib/util.moon | 112 ----------------- 29 files changed, 1535 insertions(+), 1535 deletions(-) create mode 100644 alv-lib/logic.moon create mode 100644 alv-lib/math.moon create mode 100644 alv-lib/midi.moon create mode 100644 alv-lib/midi/core.moon create mode 100644 alv-lib/midi/launchctl.moon create mode 100644 alv-lib/osc.moon create mode 100644 alv-lib/pilot.moon create mode 100644 alv-lib/random.moon create mode 100644 alv-lib/sc.moon create mode 100644 alv-lib/string.moon create mode 100644 alv-lib/time.moon create mode 100644 alv-lib/util.moon delete mode 100644 lib/logic.moon delete mode 100644 lib/math.moon delete mode 100644 lib/midi.moon delete mode 100644 lib/midi/core.moon delete mode 100644 lib/midi/launchctl.moon delete mode 100644 lib/osc.moon delete mode 100644 lib/pilot.moon delete mode 100644 lib/random.moon delete mode 100644 lib/sc.moon delete mode 100644 lib/string.moon delete mode 100644 lib/time.moon delete mode 100644 lib/util.moon diff --git a/Makefile b/Makefile index 00c307b..3f3dfff 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -MODULES=$(wildcard lib/*.moon) lib/midi/launchctl.moon -MODREFS=$(MODULES:lib/%.moon=docs/reference/%.html) +MODULES=$(wildcard alv-lib/*.moon) alv-lib/midi/launchctl.moon +MODREFS=$(MODULES:alv-lib/%.moon=docs/reference/%.html) CORE=$(wildcard alv/*.moon alv/**/*.moon) $(wildcard alv/*.md) DEPS=alv/version.moon extra/docs.moon extra/layout.moon extra/dom.moon @@ -17,10 +17,10 @@ docs/%.html: docs/%.md $(DEPS) @echo "building page $<" moon extra/docs.moon $@ markdown $< -docs/reference/%.html: lib/%.moon $(DEPS) +docs/reference/%.html: alv-lib/%.moon $(DEPS) @echo "building docs for $<" @mkdir -p `dirname $@` - moon extra/docs.moon $@ module lib.$(subst /,.,$*) $(subst /,.,$*) + moon extra/docs.moon $@ module alv-lib.$(subst /,.,$*) $(subst /,.,$*) docs/reference/index.html: $(MODREFS) $(DEPS) moon extra/docs.moon $@ reference $(MODULES) diff --git a/alv-lib/logic.moon b/alv-lib/logic.moon new file mode 100644 index 0000000..62de1c1 --- /dev/null +++ b/alv-lib/logic.moon @@ -0,0 +1,153 @@ +import Op, ValueStream, Input, Error, val from require 'alv.base' + +all_same = (first, list) -> + for v in *list + if v != first + return false + + first + +tobool = (val) -> + switch val + when false, nil, 0 + false + else + true + +class ReduceOp extends Op + pattern = val! + val! * 0 + setup: (inputs) => + @out or= ValueStream 'bool' + { first, rest } = pattern\match inputs + super + first: Input.hot first + rest: [Input.hot v for v in *rest] + + tick: => + { :first, :rest } = @unwrap_all! + accum = tobool first + for val in *rest + accum = @.fn accum, tobool val + + @out\set accum + +eq = ValueStream.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 + pattern = val! + val! * 0 + setup: (inputs) => + @out or= ValueStream 'bool', false + { first, rest } = pattern\match inputs + same = all_same first\type!, [i\type! for i in *rest] + + super if same + { + first: Input.hot first + rest: [Input.hot 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 + + @out\set equal + +not_eq = ValueStream.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." + + value: class extends Op + setup: (inputs) => + @out or= ValueStream 'bool', false + assert #inputs > 1, Error 'argument', "need at least two values" + super [Input.hot v for v in *inputs] + + 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 + + break unless diff + + @out\set diff + +and_ = ValueStream.meta + meta: + name: 'and' + summary: "Logical AND." + examples: { '(and a b [c…])' } + value: class extends ReduceOp + fn: (a, b) -> a and b + +or_ = ValueStream.meta + meta: + name: 'or' + summary: "Logical OR." + examples: { '(or a b [c…])' } + value: class extends ReduceOp + fn: (a, b) -> a or b + +not_ = ValueStream.meta + meta: + name: 'not' + summary: "Logical NOT." + examples: { '(not a)' } + + value: class extends Op + new: => super 'bool' + + setup: (inputs) => + { value } = match 'any', inputs + super value: Input.hot value + + tick: => @out\set not tobool @inputs.value! + +bool = ValueStream.meta + meta: + name: 'bool' + summary: "Cast value to bool." + examples: { '(bool a)' } + description: "`false` if a is `false`, `nil` or `0`, `true` otherwise." + + value: class extends Op + setup: (inputs) => + @out or= ValueStream 'bool' + { value } = val!\match inputs + super value: Input.hot value + + tick: => @out\set tobool @inputs\value! + +{ + :eq, '==': eq + 'not-eq': not_eq, '!=': not_eq + and: and_ + or: or_ + not: not_ + :bool +} diff --git a/alv-lib/math.moon b/alv-lib/math.moon new file mode 100644 index 0000000..6f5f3fb --- /dev/null +++ b/alv-lib/math.moon @@ -0,0 +1,192 @@ +import Op, ValueStream, Error, Input, val from require 'alv.base' +unpack or= table.unpack + +class ReduceOp extends Op + pattern = val.num + val.num*0 + setup: (inputs) => + @out or= ValueStream 'num' + { first, rest } = pattern\match inputs + super + first: Input.hot first + rest: [Input.hot v for v in *rest] + + tick: => + { :first, :rest } = @unwrap_all! + accum = first + for val in *rest + accum = @.fn accum, val + @out\set accum + +func_op = (func, pattern) -> + class extends Op + + setup: (inputs) => + @out or= ValueStream 'num' + params = pattern\match inputs + super [Input.hot p for p in *params] + + tick: => @out\set func unpack @unwrap_all! + +func_def = (name, args, func, summary, pattern) -> + ValueStream.meta + meta: + :name + :summary + examples: { "(#{name} #{args})" } + value: func_op func, pattern or val.num\rep 1, 1 + +evenodd_op = (remainder) -> + class extends Op + pattern = val.num + -val.num + setup: (inputs) => + @out or= ValueStream 'bool' + { val, div } = pattern\match inputs + super + val: Input.hot val + div: Input.hot div or ValueStream.num 2 + + tick: => + { :val, :div } = @unwrap_all! + @out\set (val % div) == remainder + +add = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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).", val.num\rep(2, 2) +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.", val.num*2 +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 + + :even, :odd + + :mix + :min, :max + + pi: with ValueStream.wrap math.pi + .meta = summary: 'The pi constant.' + tau: with ValueStream.wrap math.pi*2 + .meta = summary: 'The tau constant.' + huge: with ValueStream.wrap math.huge + .meta = summary: 'Positive infinity constant.' + + :sin, :cos, :tan + :asin, :acos, :atan, :atan2 + :sinh, :cosh, :tanh + + :floor, :ceil, :abs + :exp, :log, :log10, :sqrt +} diff --git a/alv-lib/midi.moon b/alv-lib/midi.moon new file mode 100644 index 0000000..88d9547 --- /dev/null +++ b/alv-lib/midi.moon @@ -0,0 +1,99 @@ +import ValueStream, EventStream, Op, Input, val, evt from require 'alv.base' +import input, output, inout, apply_range from require 'alv-lib.midi.core' + +gate = ValueStream.meta + meta: + name: 'gate' + summary: "gate from note-on and note-off messages." + examples: { '(midi/gate [port] note [chan])' } + + value: class extends Op + pattern = -evt['midi/port'] + val.num -val.num + setup: (inputs, scope) => + @out or= ValueStream 'bool' + { port, note, chan } = pattern\match inputs + super + port: Input.hot port or scope\get '*midi*' + note: Input.hot note + chan: Input.hot chan or ValueStream.num -1 + + tick: => + { :port, :note, :chan } = @inputs + + if note\dirty! or chan\dirty! + @out\set false + + if port\dirty! + for msg in *port! + 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 = ValueStream.meta + meta: + name: 'trig' + summary: "`bang`s from note-on messages." + examples: { '(midi/trig [port] note [chan])' } + + value: class extends Op + pattern = -evt['midi/port'] + val.num -val.num + setup: (inputs, scope) => + @out or= EventStream 'bang' + { port, note, chan } = pattern\match inputs + super + port: Input.hot port or scope\get '*midi*' + note: Input.cold note + chan: Input.cold chan or ValueStream.num -1 + + tick: => + { :port, :note, :chan } = @inputs + + for msg in *port! + if msg.a == note! and (chan! == -1 or msg.chan == chan!) + if msg.status == 'note-on' + @out\add true + +cc = ValueStream.meta + meta: + name: 'cc' + 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[ +- 'rad' [ 0 - tau[ +- 'deg' [ 0 - 360[ +- (num) [ 0 - num[" + + value: class extends Op + pattern = -evt['midi/port'] + val.num + -val.num + -val.num + setup: (inputs, scope) => + { port, cc, chan, range } = pattern\match inputs + super + port: Input.hot port or scope\get '*midi*' + cc: Input.cold cc + chan: Input.cold chan or ValueStream.num -1 + range: Input.cold range or ValueStream.str 'uni' + + @out or= ValueStream 'num', apply_range @inputs.range, 0 + + tick: => + { :port, :cc, :chan, :range } = @inputs + for msg in *port! + if msg.status == 'control-change' and + (chan! == -1 or msg.chan == chan!) and + msg.a == cc! + @out\set apply_range range, msg.b + +{ + :input + :output + :inout + :gate + :trig + :cc +} diff --git a/alv-lib/midi/core.moon b/alv-lib/midi/core.moon new file mode 100644 index 0000000..ddb1e8a --- /dev/null +++ b/alv-lib/midi/core.moon @@ -0,0 +1,122 @@ +import ValueStream, IOStream, Op, Input, Error, val from require 'alv.base' +import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' + +bit = do + ok, bit = pcall require, 'bit32' + if ok then bit else require 'bit' +import band, bor, lshift, rshift from bit + +MIDI = { + [0x9]: 'note-on' + [0x8]: 'note-off' + + [0xa]: 'after-key' + [0xd]: 'after-channel' + + [0xb]: 'control-change' + [0xe]: 'pitch-bend' + [0xc]: 'program-change' +} + +rMIDI = {v,k for k,v in pairs MIDI} + +find_port = (Klass, name) -> + with Klass RtMidi.Api.UNIX_JACK + id = nil + for port=1, \getportcount! + if name == \getportname port + id = port + break + + \openport id + +class MidiPort extends IOStream + new: => super 'midi/port' + + setup: (inp, out) => + @inp = inp and find_port RtMidiIn, inp + @out = out and find_port RtMidiOut, out + + tick: => + return unless @inp + while true + delta, bytes = @inp\getmessage! + break unless delta + + { status, a, b } = bytes + chan = band status, 0xf + status = MIDI[rshift status, 4] + @add { :status, :chan, :a, :b } + + send: (status, chan, a, b) => + assert @out, Error 'type', "#{@} is not an output or bidirectional port" + if 'string' == type 'status' + status = bor (lshift rMIDI[status], 4), chan + @out\sendmessage status, a, b + +class PortOp extends Op + new: (...) => + super ... + @out or= MidiPort! + + tick: (inp, out) => + { :inp, :out } = @unwrap_all! + @out\setup inp, out + +input = ValueStream.meta + meta: + name: 'input' + summary: "Create a MIDI input port." + examples: { '(midi/input name)' } + + value: class extends PortOp + setup: (inputs) => + name = val.str\match inputs + super inp: Input.hot name + +output = ValueStream.meta + meta: + name: 'output' + summary: "Create a MIDI output port." + examples: { '(midi/output name)' } + + value: class extends PortOp + setup: (inputs) => + name = val.str\match inputs + super out: Input.hot name + +inout = ValueStream.meta + meta: + name: 'inout' + summary: "Create a bidirectional MIDI port." + examples: { '(midi/inout name)' } + + value: class extends PortOp + setup: (inputs) => + { inp, out } = (val.str + val.str)\match inputs + super + inp: Input.hot inp + out: Input.hot out + +apply_range = (range, val) -> + if range\type! == 'str' + switch range! + when 'raw' then val + when 'uni' then val / 128 + when 'bip' then val / 64 - 1 + when 'rad' then val / 64 * math.pi + when 'deg' then val / 128 * 360 + else + error Error 'argument', "unknown range '#{range!}'" + elseif range.type == 'num' + val / 128 * range! + else + error Error 'argument', "range has to be a string or number" + +{ + :input + :output + :inout + :apply_range + :bit +} diff --git a/alv-lib/midi/launchctl.moon b/alv-lib/midi/launchctl.moon new file mode 100644 index 0000000..90e8956 --- /dev/null +++ b/alv-lib/midi/launchctl.moon @@ -0,0 +1,194 @@ +import ValueStream, EventStream, Op, Input, val, evt from require 'alv.base' +import apply_range, bit from require 'alv-lib.midi.core' +import bor, lshift from bit + +color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4) + +cc_seq = ValueStream.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. + +range can be one of: +- 'raw' [ 0 - 128[ +- 'uni' [ 0 - 1[ (default) +- 'bip' [-1 - 1[ +- 'rad' [ 0 - tau[ +- 'deg' [ 0 - 360[ +- (num) [ 0 - num[" + + value: class extends Op + num = val.num + pattern = -evt['midi/port'] + num + num + num + -num + -(val.str + num) + setup: (inputs, scope) => + { port, i, start, chan, steps, range } = pattern\match inputs + + super + port: Input.hot port or scope\get '*ctrl*' + i: Input.hot i + start: Input.hot start + chan: Input.hot chan + steps: Input.hot steps or ValueStream.num 8 + range: Input.hot range or ValueStream.str 'uni' + + @state or= {} + @out or= ValueStream 'num', 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! + 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 = ValueStream.meta + meta: + name: 'gate-seq' + summary: "MIDI Gate-Sequencer." + examples: { '(launchctl/gate-seq [port] i start chan [steps])' } + description: " +Send `true` or `false` for the `i`-th note-button (MIDI-notes starting from +`start`). `steps` defaults to 8." + + value: class extends Op + pattern = -evt['midi/port'] + val.num + val.num + val.num + -val.num + setup: (inputs, scope) => + @out or= ValueStream 'bool' + @state or= {} + { port, i, start, chan, steps } = pattern\match inputs + + super + port: Input.hot port or scope\get '*ctrl*' + i: Input.hot i + start: Input.hot start + chan: Input.hot chan + steps: Input.hot steps or ValueStream.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) => + start, chan = @inputs.start!, @inputs.chan! + @inputs.port.stream\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! + 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] + +trig_seq = ValueStream.meta + meta: + name: 'trig-seq' + summary: "MIDI Trigger-Sequencer." + examples: { '(launchctl/trig-seq [port] i start chan [steps])' } + description: " +Send bangs for the `i`-th note-button (MIDI-notes starting from `start`). +`steps` defaults to 8." + + value: class extends Op + pattern = -evt['midi/port'] + val.num + val.num + val.num + -val.num + setup: (inputs, scope) => + @out or= EventStream 'bang' + @state or= {} + { port, i, start, chan, steps } = pattern\match inputs + + super + port: Input.hot port or scope\get '*ctrl*' + i: Input.hot i + start: Input.hot start + chan: Input.hot chan + steps: Input.hot steps or ValueStream.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) => + start, chan = @inputs.start!, @inputs.chan! + @inputs.port.stream\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! + 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 + + if @state[curr_i+1] + @out\add true + +{ + 'cc-seq': cc_seq + 'gate-seq': gate_seq + 'trig-seq': trig_seq +} diff --git a/alv-lib/osc.moon b/alv-lib/osc.moon new file mode 100644 index 0000000..b7e25e7 --- /dev/null +++ b/alv-lib/osc.moon @@ -0,0 +1,87 @@ +import Op, ValueStream, Input, val, evt from require 'alv.base' +import pack from require 'osc' +import dns, udp from require 'socket' + +unpack or= table.unpack + +connect = ValueStream.meta + meta: + name: 'connect' + summary: "Create a UDP remote." + examples: { '(osc/connect host port)' } + + value: class extends Op + pattern = val.str + val.num + setup: (inputs) => + @out or= ValueStream 'udp/socket' + { host, port } = pattern\match inputs + super + host: Input.hot host + port: Input.hot port + + tick: => + { :host, :port } = @unwrap_all! + ip = dns.toip host + + @out\set with sock = udp! + \setpeername ip, port + +send = ValueStream.meta + meta: + name: 'send' + summary: "Send events via OSC." + examples: { '(osc/send [socket] path evt)' } + description: "Sends an OSC message with `evt` as an argument. + +- `socket` should be a `udp/socket` value. This argument can be omitted and the + value be passed as a dynamic definition in `*sock*` instead. +- `path` is the OSC path to send the message to. It should be a string-value. +- `evt` is the argument to send. It should be an event stream." + value: class extends Op + pattern = -val['udp/socket'] + val.str + evt! + setup: (inputs, scope) => + { socket, path, value } = pattern\match inputs + super + socket: Input.cold socket or scope\get '*sock*' + path: Input.cold path + value: Input.hot value + + tick: => + { :socket, :path, :value } = @unwrap_all! + for val in *value + msg = pack path, if 'table' == type val then unpack val else val + socket\send msg + +sync = ValueStream.meta + meta: + name: 'sync' + summary: "Synchronize a value via OSC." + examples: { '(osc/sync [socket] path val)' } + description: "sends a message whenever any parameter is dirty." + description: "Sends an OSC message with `val` as an argument whenever any +of the arguments change. + +- `socket` should be a `udp/socket` value. This argument can be omitted and the + value be passed as a dynamic definition in `*sock*` instead. +- `path` is the OSC path to send the message to. It should be a string-value. +- `val` is the value to Synchronize. It should be a value stream." + + value: class extends Op + pattern = -val['udp/socket'] + val.str + val! + setup: (inputs, scope) => + { socket, path, value } = pattern\match inputs + super + socket: Input.hot socket or scope\get '*sock*' + path: Input.hot path + value: Input.hot value + + tick: => + { :socket, :path, :value } = @unwrap_all! + msg = pack path, value + socket\send msg + +{ + :connect + :send + :sync +} diff --git a/alv-lib/pilot.moon b/alv-lib/pilot.moon new file mode 100644 index 0000000..0d6eefb --- /dev/null +++ b/alv-lib/pilot.moon @@ -0,0 +1,90 @@ +import Op, ValueStream, Input, val, evt from require 'alv.base' +import udp from require 'socket' + +local conn + +hex = "0123456789abcdef" +encode = (arg) -> + switch type arg + when 'number' then + i = 1 + math.floor arg + hex\sub i, i + when 'string' then arg + else error "invalid type: #{type arg}" + +send = (...) -> + str = '' + for i = 1, select '#', ... + tbl = select i, ... + str ..= table.concat [encode v for v in *tbl] + conn or= udp! + conn\sendto str, '127.0.0.1', 49161 + +arg = val.num / val.str + +play = ValueStream.meta + meta: + name: 'play' + summary: "Play a note when a bang arrives." + examples: { '(pilot/play trig ch oct note [vel [len]])' } + + value: class extends Op + pattern = evt.bang + arg^5 + setup: (inputs) => + { trig, args } = pattern\match inputs + super + trig: Input.hot trig + args: [Input.cold a for a in *args] + + tick: => + { :trig, :args } = @inputs + for _ in *trig! + send [a! for a in *@inputs.args] + +play_ = ValueStream.meta + meta: + name: 'play!' + summary: "Play a note when a note arrives." + examples: { '(pilot/play! ch oct note [vel [len]])' } + + value: class extends Op + pattern = arg + arg + (evt.num / evt.str) + arg^2 + setup: (inputs) => + { chan, octv, note, args } = pattern\match inputs + super + chan: Input.cold chan + octv: Input.cold octv + note: Input.hot note + args: [Input.cold a for a in *args] + + tick: => + { :chan, :octv, :note, :args } = @inputs + args = for a in *args do a! + for note in *note! + send { chan!, octv!, note }, args + +effect = ValueStream.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'" + + value: class extends Op + pattern = val.str + arg + arg + setup: (inputs) => + { which, a, b } = pattern\match inputs + super { + Input.hot which + Input.hot a + Input.hot b + } + + tick: => + send @unwrap_all! + +{ + :play + 'play!': play_ + :effect +} diff --git a/alv-lib/random.moon b/alv-lib/random.moon new file mode 100644 index 0000000..8c4d096 --- /dev/null +++ b/alv-lib/random.moon @@ -0,0 +1,85 @@ +import ValueStream, Error, Op, Input, val, evt from require 'alv.base' + +apply_range = (range, val) -> + if range\type! == 'str' + switch range! + when 'uni' then val + when 'bip' then val*2 - 1 + when 'rad' then val*2 * math.pi + when 'deg' then val * 360 + else + error Error 'argument', "unknown range '#{range!}'" + elseif range\type! == 'num' + val * range! + else + error Error 'argument', "range has to be a string or number" + +range_doc = " +range can be one of: +- 'uni' [ 0 - 1[ (default) +- 'bip' [-1 - 1[ +- 'rad' [ 0 - tau[ +- 'deg' [ 0 - 360[ +- (num) [ 0 - num[" + +pattern = -evt.bang + -(val.num / val.str) + +num = ValueStream.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}" + + value: class extends Op + new: (...) => + super ... + @out or= ValueStream 'num' + @state or @gen! + + gen: => @state = math.random! + + setup: (inputs) => + { trig, range } = pattern\match inputs + super + trig: trig and Input.hot trig + range: Input.hot range or ValueStream.str 'uni' + + tick: => + @gen! if @inputs.trig and @inputs.trig\dirty! + @out\set apply_range @inputs.range, @state + +vec = (n) -> + ValueStream.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 ... + @out or= ValueStream "vec#{n}" + @state or @gen! + + gen: => @state = for i=1,n do math.random! + + setup: (inputs) => + { trig, range } = pattern\match inputs + super + trig: trig and Input.hot trig + range: Input.hot range or ValueStream.str 'uni' + + tick: => + @gen! if @inputs.trig and @inputs.trig\dirty! + @out\set [apply_range @inputs.range, v for v in *@state] + +{ + :num + vec2: vec 2 + vec3: vec 3 + vec4: vec 4 +} diff --git a/alv-lib/sc.moon b/alv-lib/sc.moon new file mode 100644 index 0000000..2857392 --- /dev/null +++ b/alv-lib/sc.moon @@ -0,0 +1,83 @@ +import Op, ValueStream, Input, val, evt from require 'alv.base' +import pack from require 'osc' +import dns, udp from require 'socket' + +unpack or= table.unpack + +play = ValueStream.meta + meta: + name: 'play' + summary: 'Play a SuperCollider SynthDef on bangs.' + examples: { '(play [socket] synth trig [param val…])' } + description: " +Plays the synth `synth` on the `udp/socket` `socket` whenever `trig` is live. + +- `socket` should be a `udp/socket` value. This argument can be omitted and the + value be passed as a dynamic definition in `*sock*` instead. +- `synth` is the SC synthdef name. It should be a string-value. +- `trig` is the trigger signal. It should be a stream of bang-events. +- `param` is the name of a synthdef parameter. It should be a string-value." + value: class extends Op + pattern = -val['udp/socket'] + val.str + evt.bang + (val.str + val.num)\rep 0 + setup: (inputs, scope) => + { socket, synth, trig, ctrls } = pattern\match inputs + + flat_ctrls = {} + for { key, value } in *ctrls + table.insert flat_ctrls, key + table.insert flat_ctrls, value + + super + trig: Input.hot trig + socket: Input.cold socket or scope\get '*sock*' + synth: Input.cold synth + ctrls: [Input.cold v for v in *flat_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 + +play_ = ValueStream.meta + meta: + name: 'play!' + summary: 'Play a SuperCollider SynthDef on events.' + examples: { '(play [socket] synth [param evt/val…])' } + description: " +Plays the synth `synth` on the `udp/socket` `socket` whenever any `evt` is live. + +- `socket` should be a `udp/socket` value. This argument can be omitted and the + value be passed as a dynamic definition in `*sock*` instead. +- `synth` is the SC synthdef name. It should be a string-value. +- `param` is the name of a synthdef parameter. It should be a string-value. +- `val` and `evt` are the parameter values to send. They should be number + streams. Incoming events will cause a note to be played, while value changes + will not." + value: class extends Op + pattern = -val['udp/socket'] + val.str + (val.str + (val.num / evt.num))\rep 0 + setup: (inputs, scope) => + { socket, synth, trig, ctrls } = pattern\match inputs + + flat = {} + for { key, value } in *ctrls + table.insert flat, Input.cold key + table.insert flat, if value\metatype! == 'event' + Input.hot value + else + Input.cold value + + super + socket: Input.cold socket or scope\get '*sock*' + synth: Input.cold synth + ctrls: flat + + tick: => + { :socket, :synth, :ctrls } = @unwrap_all! + msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls + socket\send msg + +{ + :play + 'play!': play_ +} diff --git a/alv-lib/string.moon b/alv-lib/string.moon new file mode 100644 index 0000000..faa7ab3 --- /dev/null +++ b/alv-lib/string.moon @@ -0,0 +1,18 @@ +import Op, ValueStream, Input from require 'alv.base' + +str = ValueStream.meta + meta: + name: 'str' + summary: "Concatenate/stringify values." + examples: { '(.. v1 [v2…])', '(str v1 [v2…])' } + value: class extends Op + setup: (inputs) => + @out or= ValueStream 'string' + super [Input.hot v for v in *inputs] + + tick: => + @out\set table.concat [tostring v! for v in *@inputs] + +{ + :str, '..': str +} diff --git a/alv-lib/time.moon b/alv-lib/time.moon new file mode 100644 index 0000000..2589d96 --- /dev/null +++ b/alv-lib/time.moon @@ -0,0 +1,285 @@ +import + ValueStream, EventStream, IOStream, + Error, Op, Input, val, evt +from require 'alv.base' +import monotime from require 'system' + +class Clock extends IOStream + new: (@frametime) => + super 'clock' + + return unless monotime + @last = monotime! + @dt = 0 + @is_dirty = false + + tick: => + time = monotime! + @dt = time - @last + if @dt >= @frametime + @add { dt: @dt, :time } + @last = time + +class ScaledClock extends EventStream + set: (val) => @value + unwrap: => @value + +clock = ValueStream.meta + meta: + name: 'clock' + summary: "Create a clock source." + examples: { '(clock)', '(clock fps)' } + description: "Creates a new clock event stream. + +The clock event stream is an IO that triggers other operators at a fixed +frame rate. + +- `fps` has to be an eval-time constant and defaults to `60` if omitted." + value: class extends Op + new: (...) => + super ... + @out or= Clock! + + setup: (inputs) => + fps = (-val.num)\match inputs + super fps: Input.hot fps or ValueStream.num 60 + @out.frametime = 1 / @inputs.fps! + + tick: => + @out.frametime = 1 / @inputs.fps! + +scale_time = ValueStream.meta + meta: + name: 'scale-time' + summary: "Scale clock time." + examples: { '(scale-time [clock] scale)' } + description: "Creates a new clock event stream scaled by `scale`. + +- `clock` should be a `time/clock` event stream. This argument can be omitted + and the stream be passed as a dynamic definition in `*clock*` instead. +- `scale` should be a num-value." + value: class extends Op + new: (...) => + super ... + @out or= EventStream 'clock' + + pattern = -evt.clock + val.num + -val.str + setup: (inputs, scope) => + { clock, scale } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + scale: Input.cold scale + + tick: => + scale = @inputs.scale! + for evt in *@inputs.clock! + @out\add {k, v*scale for k,v in pairs evt} + +lfo = ValueStream.meta + meta: + name: 'lfo' + summary: "Low-frequency oscillator." + examples: { '(lfo [clock] freq [wave])' } + description: "Oscillates betwen `0` and `1` at the frequency `freq`. + +- `clock` should be a `time/clock` event stream. This argument can be omitted + and the stream be passed as a dynamic definition in `*clock*` instead. +- `freq` should be a num-value. +- `wave` selects the wave shape from one of the following: + - `'sin'` (the default) + - `'saw'` + - `'tri'`" + value: class extends Op + new: (...) => + super ... + @state or= 0 + @out or= ValueStream 'num' + + default_wave = ValueStream.str 'sin' + pattern = -evt.clock + val.num + -val.str + setup: (inputs, scope) => + { clock, freq, wave } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + freq: Input.cold freq + wave: Input.hot wave or default_wave + + tau = math.pi * 2 + tick: => + for tick in *@inputs.clock! + @state += tick.dt * @inputs.freq! + + @out\set switch @inputs.wave! + when 'sin' then .5 + .5 * math.cos @state * tau + when 'saw' then @state % 1 + when 'tri' then math.abs (2*@state % 2) - 1 + else error Error 'argument', "unknown wave type '#{wave}'" + +ramp = ValueStream.meta + meta: + name: 'ramp' + summary: "Sawtooth LFO." + examples: { '(ramp [clock] period [max])' } + description: "Ramps from `0` to `max` once every `period` seconds. + +- `clock` should be a `time/clock` event stream. This argument can be omitted + and the stream be passed as a dynamic definition in `*clock*` instead. +- `period` should be a num-value. +- `max` should be a num-value and defaults to `period` if omitted." + value: class extends Op + new: (...) => + super ... + @state or= 0 + @out or= ValueStream 'num' + + pattern = -evt.clock + val.num + -val.num + setup: (inputs, scope) => + { clock, period, max } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period + max: max and Input.cold max + + tick: => + for tick in *@inputs.clock! + period = @inputs.period! + max = (@inputs.max or @inputs.period)! + @phase += tick.dt / period + + while @phase >= 1 + @phase -= 1 + + @out\set @phase * max + +tick = ValueStream.meta + meta: + name: 'tick' + summary: "Count ticks." + examples: { '(tick [clock] period)' } + description: "Counts upwards by one every `period` seconds. + +- `clock` should be a `time/clock` event stream. This argument can be omitted + and the stream be passed as a dynamic definition in `*clock*` instead. +- `period` should be a num-value. +- returns a `num` value that increases by 1 every `period`." + value: class extends Op + new: (...) => + super ... + @state or= { phase: 0, count: 0 } + @out or= ValueStream 'num', @state.count + + pattern = -evt.clock + val.num + setup: (inputs, scope) => + { clock, period } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period + + tick: => + for tick in *@inputs.clock! + @state.phase += tick.dt / @inputs.period! + + while @state.phase >= 1 + @state.phase -= 1 + @state.count += 1 + @out\set @state.count + +every = ValueStream.meta + meta: + name: 'every' + summary: "Emit events regularly." + examples: { '(every [clock] period [evt])' } + description: "Emits `evt` as an event once every `period` seconds. + +- `clock` should be a `time/clock` event stream. This argument can be omitted + and the stream be passed as a dynamic definition in `*clock*` instead. +- `period` should be a num-value. +- `evt` can be a value of any type. It defaults to `bang`. +- the return type will be an event stream with the same type as `evt`." + value: class extends Op + new: (...) => + super ... + @state or= 0 + + pattern = -evt.clock + val.num + -val! + setup: (inputs, scope) => + { clock, period, evt } = pattern\match inputs + super + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period + evt: Input.cold evt or ValueStream 'bang', true + @out = EventStream @inputs.evt\type! + + tick: => + for tick in *@inputs.clock! + @state += tick.dt / @inputs.period! + + while @state >= 1 + @state -= 1 + @out\add @inputs.evt! + +sequence = ValueStream.meta + meta: + name: 'sequence' + summary: "Emit a sequence of values as events over time." + examples: { '(sequence [clock] delay0 evt1 delay1 evt2 delay2…)' } + description: " +Emits `evt1`, `evt2`, … as events with delays `delay0`, `delay1`, … in between. + +- `clock` should be a `time/clock` event stream. This argument can be omitted + and the stream be passed as a dynamic definition in `*clock*` instead. +- `delay0`, `delay1`, … must be num-values. +- `evt1`, `evt2`, … must be values of the same type. +- the return type will be an event stream with the same type as the `evt`s." + value: class extends Op + new: (...) => + super ... + @state or= { i: 1, t: 0 } + + pair = (val! + val.num)\named('value', 'delay') + pattern = -evt.clock + val.num + pair*0 + + inputify = (step) -> + { + delay: Input.cold step.delay + value: if step.value then Input.hot step.value + } + + setup: (inputs, scope) => + { clock, first, steps } = pattern\match inputs + @out = EventStream steps[1].value\type! + table.insert steps, 1, { delay: first } + super + clock: Input.hot clock or scope\get '*clock*' + steps: [inputify step for step in *steps] + + tick: => + for tick in *@inputs.clock! + @state.t += tick.dt + + change, current = false, nil + while true + current = @inputs.steps[@state.i] + if @state.t >= current.delay! + @state.t -= current.delay! + @state.i = 1 + (@state.i % #@inputs.steps) + change = true + else + break + + if current.value and (change or current.value\dirty!) + @out\add current.value! + +{ + :clock + 'scale-time': scale_time + :lfo + :ramp + :tick + :every + :sequence + '*clock*': with Clock 1/60 + .meta = + name: '*clock*' + summary: 'Default clock source (60fps).' +} diff --git a/alv-lib/util.moon b/alv-lib/util.moon new file mode 100644 index 0000000..dc133e4 --- /dev/null +++ b/alv-lib/util.moon @@ -0,0 +1,112 @@ +import Op, ValueStream, EventStream, Input, val, evt from require 'alv.base' + +all_same = (list) -> + for v in *list[2,] + if v != list[1] + return false + + list[1] + +switch_ = ValueStream.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 + val_or_evt = (val! / evt!)! + pattern = (val.num / val.bool) + val_or_evt*0 + setup: (inputs) => + { i, values } = pattern\match inputs + + @state = values[1].value.__class == ValueStream + + @out = if @state + ValueStream values[1]\type! + else + EventStream values[1]\type! + + super + i: Input.hot i + values: [Input.hot 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 @state + @out\set active and active! + else + if active and active\dirty! + for event in *active! + @out\add event + +edge = ValueStream.meta + meta: + name: 'edge' + summary: "Convert rising edges to bangs." + examples: { '(edge bool)' } + + value: class extends Op + setup: (inputs) => + @out or= EventStream 'bang' + value = val.bool\match inputs + super value: Input.hot value + + tick: => + now = @inputs.value! + if now and not @state.last + @out\set true + @state.last = now + +change = ValueStream.meta + meta: + name: 'change' + summary: "Convert value changes to events." + examples: { '(change val)' } + + value: class extends Op + setup: (inputs) => + value = val!\match inputs + @out or= EventStream value\type! + super value: Input.hot value + + tick: => + now = @inputs.value! + if now != @state + @out\add @inputs.value! + @state = now + +hold = ValueStream.meta + meta: + name: 'hold' + summary: "Convert events to value changes." + examples: { '(hold evt)' } + + value: class extends Op + setup: (inputs) => + event = evt!\match inputs + @out or= ValueStream event\type! + super event: Input.hot event + + tick: => + for val in *@inputs.event! + @out\set val + +{ + 'switch': switch_ + :edge + :change + :hold +} diff --git a/alv/builtin.moon b/alv/builtin.moon index 23412bf..98b4198 100644 --- a/alv/builtin.moon +++ b/alv/builtin.moon @@ -98,7 +98,7 @@ require_ = ValueStream.meta name = result\const! L\trace @, "loading module #{name}" - scope = ValueStream.wrap require "lib.#{name\unwrap 'str'}" + scope = ValueStream.wrap require "alv-lib.#{name\unwrap 'str'}" Result :value import_ = ValueStream.meta @@ -117,8 +117,8 @@ current scope." for child in *tail name = (child\quote scope)\unwrap 'sym' - value = ValueStream.wrap require "lib.#{name}" - scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope' + value = ValueStream.wrap require "alv-lib.#{name}" + scope\set name, Result :value Result! import_star = ValueStream.meta @@ -137,8 +137,8 @@ Requires modules `sym1`, `sym2`, … and merges them into the current scope." for child in *tail name = (child\quote scope)\unwrap 'sym' - value = ValueStream.wrap require "lib.#{name}" - scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope' + value = ValueStream.wrap require "alv-lib.#{name}" + scope\use value\unwrap 'scope' Result! diff --git a/alv/config.ld b/alv/config.ld index 01160f8..6867a56 100644 --- a/alv/config.ld +++ b/alv/config.ld @@ -1,5 +1,5 @@ -project = 'alive internals' title = 'developer docs' +project = 'alive internals' description = "`alive` developer documentation" full_description = [[This section documents the *implementation* of the alive @@ -12,5 +12,5 @@ If you are looking for the language reference for users, head over to the format = 'discount' style = 'docs' template = 'docs' -topics={'alv/extensions.md'} dir = 'docs/internals' +topics = {'alv/extensions.md'} diff --git a/alv/extensions.md b/alv/extensions.md index 82a4d10..89d1a89 100644 --- a/alv/extensions.md +++ b/alv/extensions.md @@ -2,9 +2,9 @@ Extensions for `alive` are implemented in [Lua][lua] or [MoonScript][moonscript] (which runs as Lua). When an `alive` module is [`(require)`][builtins-req]d, -alive looks for a Lua module `lib.[module]`. You can simply add a new file with -extension `.lua` or `.moon` in the `lib` directory of your alive installation or -somewhere else in your `LUA_PATH`. +alive looks for a Lua module `alv-lib.[module]`. You can simply add a new file +with extension `.lua` or `.moon` in the `alv-lib` directory of your alive +installation or somewhere else in your `LUA_PATH`. To write extensions, a number of classes and utilities are required. All of these are exported in the `base` module. diff --git a/extra/docs.moon b/extra/docs.moon index aee7970..6ed5ea0 100644 --- a/extra/docs.moon +++ b/extra/docs.moon @@ -58,7 +58,7 @@ spit OUT, switch command p autoref "These modules can be imported using [require][], " .. "[import][] and [import*][]." ul for file in *arg[3,] - path = file\match '^lib/(.*)%.moon$' + path = file\match '^alv%-lib/(.*)%.moon$' name = path\gsub '/', '.' li a (code name), href: "#{path}.html" } diff --git a/lib/logic.moon b/lib/logic.moon deleted file mode 100644 index 62de1c1..0000000 --- a/lib/logic.moon +++ /dev/null @@ -1,153 +0,0 @@ -import Op, ValueStream, Input, Error, val from require 'alv.base' - -all_same = (first, list) -> - for v in *list - if v != first - return false - - first - -tobool = (val) -> - switch val - when false, nil, 0 - false - else - true - -class ReduceOp extends Op - pattern = val! + val! * 0 - setup: (inputs) => - @out or= ValueStream 'bool' - { first, rest } = pattern\match inputs - super - first: Input.hot first - rest: [Input.hot v for v in *rest] - - tick: => - { :first, :rest } = @unwrap_all! - accum = tobool first - for val in *rest - accum = @.fn accum, tobool val - - @out\set accum - -eq = ValueStream.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 - pattern = val! + val! * 0 - setup: (inputs) => - @out or= ValueStream 'bool', false - { first, rest } = pattern\match inputs - same = all_same first\type!, [i\type! for i in *rest] - - super if same - { - first: Input.hot first - rest: [Input.hot 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 - - @out\set equal - -not_eq = ValueStream.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." - - value: class extends Op - setup: (inputs) => - @out or= ValueStream 'bool', false - assert #inputs > 1, Error 'argument', "need at least two values" - super [Input.hot v for v in *inputs] - - 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 - - break unless diff - - @out\set diff - -and_ = ValueStream.meta - meta: - name: 'and' - summary: "Logical AND." - examples: { '(and a b [c…])' } - value: class extends ReduceOp - fn: (a, b) -> a and b - -or_ = ValueStream.meta - meta: - name: 'or' - summary: "Logical OR." - examples: { '(or a b [c…])' } - value: class extends ReduceOp - fn: (a, b) -> a or b - -not_ = ValueStream.meta - meta: - name: 'not' - summary: "Logical NOT." - examples: { '(not a)' } - - value: class extends Op - new: => super 'bool' - - setup: (inputs) => - { value } = match 'any', inputs - super value: Input.hot value - - tick: => @out\set not tobool @inputs.value! - -bool = ValueStream.meta - meta: - name: 'bool' - summary: "Cast value to bool." - examples: { '(bool a)' } - description: "`false` if a is `false`, `nil` or `0`, `true` otherwise." - - value: class extends Op - setup: (inputs) => - @out or= ValueStream 'bool' - { value } = val!\match inputs - super value: Input.hot value - - tick: => @out\set tobool @inputs\value! - -{ - :eq, '==': eq - 'not-eq': not_eq, '!=': not_eq - and: and_ - or: or_ - not: not_ - :bool -} diff --git a/lib/math.moon b/lib/math.moon deleted file mode 100644 index 6f5f3fb..0000000 --- a/lib/math.moon +++ /dev/null @@ -1,192 +0,0 @@ -import Op, ValueStream, Error, Input, val from require 'alv.base' -unpack or= table.unpack - -class ReduceOp extends Op - pattern = val.num + val.num*0 - setup: (inputs) => - @out or= ValueStream 'num' - { first, rest } = pattern\match inputs - super - first: Input.hot first - rest: [Input.hot v for v in *rest] - - tick: => - { :first, :rest } = @unwrap_all! - accum = first - for val in *rest - accum = @.fn accum, val - @out\set accum - -func_op = (func, pattern) -> - class extends Op - - setup: (inputs) => - @out or= ValueStream 'num' - params = pattern\match inputs - super [Input.hot p for p in *params] - - tick: => @out\set func unpack @unwrap_all! - -func_def = (name, args, func, summary, pattern) -> - ValueStream.meta - meta: - :name - :summary - examples: { "(#{name} #{args})" } - value: func_op func, pattern or val.num\rep 1, 1 - -evenodd_op = (remainder) -> - class extends Op - pattern = val.num + -val.num - setup: (inputs) => - @out or= ValueStream 'bool' - { val, div } = pattern\match inputs - super - val: Input.hot val - div: Input.hot div or ValueStream.num 2 - - tick: => - { :val, :div } = @unwrap_all! - @out\set (val % div) == remainder - -add = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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 = ValueStream.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).", val.num\rep(2, 2) -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.", val.num*2 -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 - - :even, :odd - - :mix - :min, :max - - pi: with ValueStream.wrap math.pi - .meta = summary: 'The pi constant.' - tau: with ValueStream.wrap math.pi*2 - .meta = summary: 'The tau constant.' - huge: with ValueStream.wrap math.huge - .meta = summary: 'Positive infinity constant.' - - :sin, :cos, :tan - :asin, :acos, :atan, :atan2 - :sinh, :cosh, :tanh - - :floor, :ceil, :abs - :exp, :log, :log10, :sqrt -} diff --git a/lib/midi.moon b/lib/midi.moon deleted file mode 100644 index e83b05d..0000000 --- a/lib/midi.moon +++ /dev/null @@ -1,99 +0,0 @@ -import ValueStream, EventStream, Op, Input, val, evt from require 'alv.base' -import input, output, inout, apply_range from require 'lib.midi.core' - -gate = ValueStream.meta - meta: - name: 'gate' - summary: "gate from note-on and note-off messages." - examples: { '(midi/gate [port] note [chan])' } - - value: class extends Op - pattern = -evt['midi/port'] + val.num -val.num - setup: (inputs, scope) => - @out or= ValueStream 'bool' - { port, note, chan } = pattern\match inputs - super - port: Input.hot port or scope\get '*midi*' - note: Input.hot note - chan: Input.hot chan or ValueStream.num -1 - - tick: => - { :port, :note, :chan } = @inputs - - if note\dirty! or chan\dirty! - @out\set false - - if port\dirty! - for msg in *port! - 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 = ValueStream.meta - meta: - name: 'trig' - summary: "`bang`s from note-on messages." - examples: { '(midi/trig [port] note [chan])' } - - value: class extends Op - pattern = -evt['midi/port'] + val.num -val.num - setup: (inputs, scope) => - @out or= EventStream 'bang' - { port, note, chan } = pattern\match inputs - super - port: Input.hot port or scope\get '*midi*' - note: Input.cold note - chan: Input.cold chan or ValueStream.num -1 - - tick: => - { :port, :note, :chan } = @inputs - - for msg in *port! - if msg.a == note! and (chan! == -1 or msg.chan == chan!) - if msg.status == 'note-on' - @out\add true - -cc = ValueStream.meta - meta: - name: 'cc' - 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[ -- 'rad' [ 0 - tau[ -- 'deg' [ 0 - 360[ -- (num) [ 0 - num[" - - value: class extends Op - pattern = -evt['midi/port'] + val.num + -val.num + -val.num - setup: (inputs, scope) => - { port, cc, chan, range } = pattern\match inputs - super - port: Input.hot port or scope\get '*midi*' - cc: Input.cold cc - chan: Input.cold chan or ValueStream.num -1 - range: Input.cold range or ValueStream.str 'uni' - - @out or= ValueStream 'num', apply_range @inputs.range, 0 - - tick: => - { :port, :cc, :chan, :range } = @inputs - for msg in *port! - if msg.status == 'control-change' and - (chan! == -1 or msg.chan == chan!) and - msg.a == cc! - @out\set apply_range range, msg.b - -{ - :input - :output - :inout - :gate - :trig - :cc -} diff --git a/lib/midi/core.moon b/lib/midi/core.moon deleted file mode 100644 index ddb1e8a..0000000 --- a/lib/midi/core.moon +++ /dev/null @@ -1,122 +0,0 @@ -import ValueStream, IOStream, Op, Input, Error, val from require 'alv.base' -import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' - -bit = do - ok, bit = pcall require, 'bit32' - if ok then bit else require 'bit' -import band, bor, lshift, rshift from bit - -MIDI = { - [0x9]: 'note-on' - [0x8]: 'note-off' - - [0xa]: 'after-key' - [0xd]: 'after-channel' - - [0xb]: 'control-change' - [0xe]: 'pitch-bend' - [0xc]: 'program-change' -} - -rMIDI = {v,k for k,v in pairs MIDI} - -find_port = (Klass, name) -> - with Klass RtMidi.Api.UNIX_JACK - id = nil - for port=1, \getportcount! - if name == \getportname port - id = port - break - - \openport id - -class MidiPort extends IOStream - new: => super 'midi/port' - - setup: (inp, out) => - @inp = inp and find_port RtMidiIn, inp - @out = out and find_port RtMidiOut, out - - tick: => - return unless @inp - while true - delta, bytes = @inp\getmessage! - break unless delta - - { status, a, b } = bytes - chan = band status, 0xf - status = MIDI[rshift status, 4] - @add { :status, :chan, :a, :b } - - send: (status, chan, a, b) => - assert @out, Error 'type', "#{@} is not an output or bidirectional port" - if 'string' == type 'status' - status = bor (lshift rMIDI[status], 4), chan - @out\sendmessage status, a, b - -class PortOp extends Op - new: (...) => - super ... - @out or= MidiPort! - - tick: (inp, out) => - { :inp, :out } = @unwrap_all! - @out\setup inp, out - -input = ValueStream.meta - meta: - name: 'input' - summary: "Create a MIDI input port." - examples: { '(midi/input name)' } - - value: class extends PortOp - setup: (inputs) => - name = val.str\match inputs - super inp: Input.hot name - -output = ValueStream.meta - meta: - name: 'output' - summary: "Create a MIDI output port." - examples: { '(midi/output name)' } - - value: class extends PortOp - setup: (inputs) => - name = val.str\match inputs - super out: Input.hot name - -inout = ValueStream.meta - meta: - name: 'inout' - summary: "Create a bidirectional MIDI port." - examples: { '(midi/inout name)' } - - value: class extends PortOp - setup: (inputs) => - { inp, out } = (val.str + val.str)\match inputs - super - inp: Input.hot inp - out: Input.hot out - -apply_range = (range, val) -> - if range\type! == 'str' - switch range! - when 'raw' then val - when 'uni' then val / 128 - when 'bip' then val / 64 - 1 - when 'rad' then val / 64 * math.pi - when 'deg' then val / 128 * 360 - else - error Error 'argument', "unknown range '#{range!}'" - elseif range.type == 'num' - val / 128 * range! - else - error Error 'argument', "range has to be a string or number" - -{ - :input - :output - :inout - :apply_range - :bit -} diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon deleted file mode 100644 index 42837f0..0000000 --- a/lib/midi/launchctl.moon +++ /dev/null @@ -1,194 +0,0 @@ -import ValueStream, EventStream, Op, Input, val, evt from require 'alv.base' -import apply_range, bit from require 'lib.midi.core' -import bor, lshift from bit - -color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4) - -cc_seq = ValueStream.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. - -range can be one of: -- 'raw' [ 0 - 128[ -- 'uni' [ 0 - 1[ (default) -- 'bip' [-1 - 1[ -- 'rad' [ 0 - tau[ -- 'deg' [ 0 - 360[ -- (num) [ 0 - num[" - - value: class extends Op - num = val.num - pattern = -evt['midi/port'] + num + num + num + -num + -(val.str + num) - setup: (inputs, scope) => - { port, i, start, chan, steps, range } = pattern\match inputs - - super - port: Input.hot port or scope\get '*ctrl*' - i: Input.hot i - start: Input.hot start - chan: Input.hot chan - steps: Input.hot steps or ValueStream.num 8 - range: Input.hot range or ValueStream.str 'uni' - - @state or= {} - @out or= ValueStream 'num', 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! - 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 = ValueStream.meta - meta: - name: 'gate-seq' - summary: "MIDI Gate-Sequencer." - examples: { '(launchctl/gate-seq [port] i start chan [steps])' } - description: " -Send `true` or `false` for the `i`-th note-button (MIDI-notes starting from -`start`). `steps` defaults to 8." - - value: class extends Op - pattern = -evt['midi/port'] + val.num + val.num + val.num + -val.num - setup: (inputs, scope) => - @out or= ValueStream 'bool' - @state or= {} - { port, i, start, chan, steps } = pattern\match inputs - - super - port: Input.hot port or scope\get '*ctrl*' - i: Input.hot i - start: Input.hot start - chan: Input.hot chan - steps: Input.hot steps or ValueStream.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) => - start, chan = @inputs.start!, @inputs.chan! - @inputs.port.stream\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! - 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] - -trig_seq = ValueStream.meta - meta: - name: 'trig-seq' - summary: "MIDI Trigger-Sequencer." - examples: { '(launchctl/trig-seq [port] i start chan [steps])' } - description: " -Send bangs for the `i`-th note-button (MIDI-notes starting from `start`). -`steps` defaults to 8." - - value: class extends Op - pattern = -evt['midi/port'] + val.num + val.num + val.num + -val.num - setup: (inputs, scope) => - @out or= EventStream 'bang' - @state or= {} - { port, i, start, chan, steps } = pattern\match inputs - - super - port: Input.hot port or scope\get '*ctrl*' - i: Input.hot i - start: Input.hot start - chan: Input.hot chan - steps: Input.hot steps or ValueStream.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) => - start, chan = @inputs.start!, @inputs.chan! - @inputs.port.stream\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! - 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 - - if @state[curr_i+1] - @out\add true - -{ - 'cc-seq': cc_seq - 'gate-seq': gate_seq - 'trig-seq': trig_seq -} diff --git a/lib/osc.moon b/lib/osc.moon deleted file mode 100644 index b7e25e7..0000000 --- a/lib/osc.moon +++ /dev/null @@ -1,87 +0,0 @@ -import Op, ValueStream, Input, val, evt from require 'alv.base' -import pack from require 'osc' -import dns, udp from require 'socket' - -unpack or= table.unpack - -connect = ValueStream.meta - meta: - name: 'connect' - summary: "Create a UDP remote." - examples: { '(osc/connect host port)' } - - value: class extends Op - pattern = val.str + val.num - setup: (inputs) => - @out or= ValueStream 'udp/socket' - { host, port } = pattern\match inputs - super - host: Input.hot host - port: Input.hot port - - tick: => - { :host, :port } = @unwrap_all! - ip = dns.toip host - - @out\set with sock = udp! - \setpeername ip, port - -send = ValueStream.meta - meta: - name: 'send' - summary: "Send events via OSC." - examples: { '(osc/send [socket] path evt)' } - description: "Sends an OSC message with `evt` as an argument. - -- `socket` should be a `udp/socket` value. This argument can be omitted and the - value be passed as a dynamic definition in `*sock*` instead. -- `path` is the OSC path to send the message to. It should be a string-value. -- `evt` is the argument to send. It should be an event stream." - value: class extends Op - pattern = -val['udp/socket'] + val.str + evt! - setup: (inputs, scope) => - { socket, path, value } = pattern\match inputs - super - socket: Input.cold socket or scope\get '*sock*' - path: Input.cold path - value: Input.hot value - - tick: => - { :socket, :path, :value } = @unwrap_all! - for val in *value - msg = pack path, if 'table' == type val then unpack val else val - socket\send msg - -sync = ValueStream.meta - meta: - name: 'sync' - summary: "Synchronize a value via OSC." - examples: { '(osc/sync [socket] path val)' } - description: "sends a message whenever any parameter is dirty." - description: "Sends an OSC message with `val` as an argument whenever any -of the arguments change. - -- `socket` should be a `udp/socket` value. This argument can be omitted and the - value be passed as a dynamic definition in `*sock*` instead. -- `path` is the OSC path to send the message to. It should be a string-value. -- `val` is the value to Synchronize. It should be a value stream." - - value: class extends Op - pattern = -val['udp/socket'] + val.str + val! - setup: (inputs, scope) => - { socket, path, value } = pattern\match inputs - super - socket: Input.hot socket or scope\get '*sock*' - path: Input.hot path - value: Input.hot value - - tick: => - { :socket, :path, :value } = @unwrap_all! - msg = pack path, value - socket\send msg - -{ - :connect - :send - :sync -} diff --git a/lib/pilot.moon b/lib/pilot.moon deleted file mode 100644 index 0d6eefb..0000000 --- a/lib/pilot.moon +++ /dev/null @@ -1,90 +0,0 @@ -import Op, ValueStream, Input, val, evt from require 'alv.base' -import udp from require 'socket' - -local conn - -hex = "0123456789abcdef" -encode = (arg) -> - switch type arg - when 'number' then - i = 1 + math.floor arg - hex\sub i, i - when 'string' then arg - else error "invalid type: #{type arg}" - -send = (...) -> - str = '' - for i = 1, select '#', ... - tbl = select i, ... - str ..= table.concat [encode v for v in *tbl] - conn or= udp! - conn\sendto str, '127.0.0.1', 49161 - -arg = val.num / val.str - -play = ValueStream.meta - meta: - name: 'play' - summary: "Play a note when a bang arrives." - examples: { '(pilot/play trig ch oct note [vel [len]])' } - - value: class extends Op - pattern = evt.bang + arg^5 - setup: (inputs) => - { trig, args } = pattern\match inputs - super - trig: Input.hot trig - args: [Input.cold a for a in *args] - - tick: => - { :trig, :args } = @inputs - for _ in *trig! - send [a! for a in *@inputs.args] - -play_ = ValueStream.meta - meta: - name: 'play!' - summary: "Play a note when a note arrives." - examples: { '(pilot/play! ch oct note [vel [len]])' } - - value: class extends Op - pattern = arg + arg + (evt.num / evt.str) + arg^2 - setup: (inputs) => - { chan, octv, note, args } = pattern\match inputs - super - chan: Input.cold chan - octv: Input.cold octv - note: Input.hot note - args: [Input.cold a for a in *args] - - tick: => - { :chan, :octv, :note, :args } = @inputs - args = for a in *args do a! - for note in *note! - send { chan!, octv!, note }, args - -effect = ValueStream.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'" - - value: class extends Op - pattern = val.str + arg + arg - setup: (inputs) => - { which, a, b } = pattern\match inputs - super { - Input.hot which - Input.hot a - Input.hot b - } - - tick: => - send @unwrap_all! - -{ - :play - 'play!': play_ - :effect -} diff --git a/lib/random.moon b/lib/random.moon deleted file mode 100644 index 8c4d096..0000000 --- a/lib/random.moon +++ /dev/null @@ -1,85 +0,0 @@ -import ValueStream, Error, Op, Input, val, evt from require 'alv.base' - -apply_range = (range, val) -> - if range\type! == 'str' - switch range! - when 'uni' then val - when 'bip' then val*2 - 1 - when 'rad' then val*2 * math.pi - when 'deg' then val * 360 - else - error Error 'argument', "unknown range '#{range!}'" - elseif range\type! == 'num' - val * range! - else - error Error 'argument', "range has to be a string or number" - -range_doc = " -range can be one of: -- 'uni' [ 0 - 1[ (default) -- 'bip' [-1 - 1[ -- 'rad' [ 0 - tau[ -- 'deg' [ 0 - 360[ -- (num) [ 0 - num[" - -pattern = -evt.bang + -(val.num / val.str) - -num = ValueStream.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}" - - value: class extends Op - new: (...) => - super ... - @out or= ValueStream 'num' - @state or @gen! - - gen: => @state = math.random! - - setup: (inputs) => - { trig, range } = pattern\match inputs - super - trig: trig and Input.hot trig - range: Input.hot range or ValueStream.str 'uni' - - tick: => - @gen! if @inputs.trig and @inputs.trig\dirty! - @out\set apply_range @inputs.range, @state - -vec = (n) -> - ValueStream.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 ... - @out or= ValueStream "vec#{n}" - @state or @gen! - - gen: => @state = for i=1,n do math.random! - - setup: (inputs) => - { trig, range } = pattern\match inputs - super - trig: trig and Input.hot trig - range: Input.hot range or ValueStream.str 'uni' - - tick: => - @gen! if @inputs.trig and @inputs.trig\dirty! - @out\set [apply_range @inputs.range, v for v in *@state] - -{ - :num - vec2: vec 2 - vec3: vec 3 - vec4: vec 4 -} diff --git a/lib/sc.moon b/lib/sc.moon deleted file mode 100644 index 2857392..0000000 --- a/lib/sc.moon +++ /dev/null @@ -1,83 +0,0 @@ -import Op, ValueStream, Input, val, evt from require 'alv.base' -import pack from require 'osc' -import dns, udp from require 'socket' - -unpack or= table.unpack - -play = ValueStream.meta - meta: - name: 'play' - summary: 'Play a SuperCollider SynthDef on bangs.' - examples: { '(play [socket] synth trig [param val…])' } - description: " -Plays the synth `synth` on the `udp/socket` `socket` whenever `trig` is live. - -- `socket` should be a `udp/socket` value. This argument can be omitted and the - value be passed as a dynamic definition in `*sock*` instead. -- `synth` is the SC synthdef name. It should be a string-value. -- `trig` is the trigger signal. It should be a stream of bang-events. -- `param` is the name of a synthdef parameter. It should be a string-value." - value: class extends Op - pattern = -val['udp/socket'] + val.str + evt.bang + (val.str + val.num)\rep 0 - setup: (inputs, scope) => - { socket, synth, trig, ctrls } = pattern\match inputs - - flat_ctrls = {} - for { key, value } in *ctrls - table.insert flat_ctrls, key - table.insert flat_ctrls, value - - super - trig: Input.hot trig - socket: Input.cold socket or scope\get '*sock*' - synth: Input.cold synth - ctrls: [Input.cold v for v in *flat_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 - -play_ = ValueStream.meta - meta: - name: 'play!' - summary: 'Play a SuperCollider SynthDef on events.' - examples: { '(play [socket] synth [param evt/val…])' } - description: " -Plays the synth `synth` on the `udp/socket` `socket` whenever any `evt` is live. - -- `socket` should be a `udp/socket` value. This argument can be omitted and the - value be passed as a dynamic definition in `*sock*` instead. -- `synth` is the SC synthdef name. It should be a string-value. -- `param` is the name of a synthdef parameter. It should be a string-value. -- `val` and `evt` are the parameter values to send. They should be number - streams. Incoming events will cause a note to be played, while value changes - will not." - value: class extends Op - pattern = -val['udp/socket'] + val.str + (val.str + (val.num / evt.num))\rep 0 - setup: (inputs, scope) => - { socket, synth, trig, ctrls } = pattern\match inputs - - flat = {} - for { key, value } in *ctrls - table.insert flat, Input.cold key - table.insert flat, if value\metatype! == 'event' - Input.hot value - else - Input.cold value - - super - socket: Input.cold socket or scope\get '*sock*' - synth: Input.cold synth - ctrls: flat - - tick: => - { :socket, :synth, :ctrls } = @unwrap_all! - msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls - socket\send msg - -{ - :play - 'play!': play_ -} diff --git a/lib/string.moon b/lib/string.moon deleted file mode 100644 index faa7ab3..0000000 --- a/lib/string.moon +++ /dev/null @@ -1,18 +0,0 @@ -import Op, ValueStream, Input from require 'alv.base' - -str = ValueStream.meta - meta: - name: 'str' - summary: "Concatenate/stringify values." - examples: { '(.. v1 [v2…])', '(str v1 [v2…])' } - value: class extends Op - setup: (inputs) => - @out or= ValueStream 'string' - super [Input.hot 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 deleted file mode 100644 index 2589d96..0000000 --- a/lib/time.moon +++ /dev/null @@ -1,285 +0,0 @@ -import - ValueStream, EventStream, IOStream, - Error, Op, Input, val, evt -from require 'alv.base' -import monotime from require 'system' - -class Clock extends IOStream - new: (@frametime) => - super 'clock' - - return unless monotime - @last = monotime! - @dt = 0 - @is_dirty = false - - tick: => - time = monotime! - @dt = time - @last - if @dt >= @frametime - @add { dt: @dt, :time } - @last = time - -class ScaledClock extends EventStream - set: (val) => @value - unwrap: => @value - -clock = ValueStream.meta - meta: - name: 'clock' - summary: "Create a clock source." - examples: { '(clock)', '(clock fps)' } - description: "Creates a new clock event stream. - -The clock event stream is an IO that triggers other operators at a fixed -frame rate. - -- `fps` has to be an eval-time constant and defaults to `60` if omitted." - value: class extends Op - new: (...) => - super ... - @out or= Clock! - - setup: (inputs) => - fps = (-val.num)\match inputs - super fps: Input.hot fps or ValueStream.num 60 - @out.frametime = 1 / @inputs.fps! - - tick: => - @out.frametime = 1 / @inputs.fps! - -scale_time = ValueStream.meta - meta: - name: 'scale-time' - summary: "Scale clock time." - examples: { '(scale-time [clock] scale)' } - description: "Creates a new clock event stream scaled by `scale`. - -- `clock` should be a `time/clock` event stream. This argument can be omitted - and the stream be passed as a dynamic definition in `*clock*` instead. -- `scale` should be a num-value." - value: class extends Op - new: (...) => - super ... - @out or= EventStream 'clock' - - pattern = -evt.clock + val.num + -val.str - setup: (inputs, scope) => - { clock, scale } = pattern\match inputs - super - clock: Input.hot clock or scope\get '*clock*' - scale: Input.cold scale - - tick: => - scale = @inputs.scale! - for evt in *@inputs.clock! - @out\add {k, v*scale for k,v in pairs evt} - -lfo = ValueStream.meta - meta: - name: 'lfo' - summary: "Low-frequency oscillator." - examples: { '(lfo [clock] freq [wave])' } - description: "Oscillates betwen `0` and `1` at the frequency `freq`. - -- `clock` should be a `time/clock` event stream. This argument can be omitted - and the stream be passed as a dynamic definition in `*clock*` instead. -- `freq` should be a num-value. -- `wave` selects the wave shape from one of the following: - - `'sin'` (the default) - - `'saw'` - - `'tri'`" - value: class extends Op - new: (...) => - super ... - @state or= 0 - @out or= ValueStream 'num' - - default_wave = ValueStream.str 'sin' - pattern = -evt.clock + val.num + -val.str - setup: (inputs, scope) => - { clock, freq, wave } = pattern\match inputs - super - clock: Input.hot clock or scope\get '*clock*' - freq: Input.cold freq - wave: Input.hot wave or default_wave - - tau = math.pi * 2 - tick: => - for tick in *@inputs.clock! - @state += tick.dt * @inputs.freq! - - @out\set switch @inputs.wave! - when 'sin' then .5 + .5 * math.cos @state * tau - when 'saw' then @state % 1 - when 'tri' then math.abs (2*@state % 2) - 1 - else error Error 'argument', "unknown wave type '#{wave}'" - -ramp = ValueStream.meta - meta: - name: 'ramp' - summary: "Sawtooth LFO." - examples: { '(ramp [clock] period [max])' } - description: "Ramps from `0` to `max` once every `period` seconds. - -- `clock` should be a `time/clock` event stream. This argument can be omitted - and the stream be passed as a dynamic definition in `*clock*` instead. -- `period` should be a num-value. -- `max` should be a num-value and defaults to `period` if omitted." - value: class extends Op - new: (...) => - super ... - @state or= 0 - @out or= ValueStream 'num' - - pattern = -evt.clock + val.num + -val.num - setup: (inputs, scope) => - { clock, period, max } = pattern\match inputs - super - clock: Input.hot clock or scope\get '*clock*' - period: Input.cold period - max: max and Input.cold max - - tick: => - for tick in *@inputs.clock! - period = @inputs.period! - max = (@inputs.max or @inputs.period)! - @phase += tick.dt / period - - while @phase >= 1 - @phase -= 1 - - @out\set @phase * max - -tick = ValueStream.meta - meta: - name: 'tick' - summary: "Count ticks." - examples: { '(tick [clock] period)' } - description: "Counts upwards by one every `period` seconds. - -- `clock` should be a `time/clock` event stream. This argument can be omitted - and the stream be passed as a dynamic definition in `*clock*` instead. -- `period` should be a num-value. -- returns a `num` value that increases by 1 every `period`." - value: class extends Op - new: (...) => - super ... - @state or= { phase: 0, count: 0 } - @out or= ValueStream 'num', @state.count - - pattern = -evt.clock + val.num - setup: (inputs, scope) => - { clock, period } = pattern\match inputs - super - clock: Input.hot clock or scope\get '*clock*' - period: Input.cold period - - tick: => - for tick in *@inputs.clock! - @state.phase += tick.dt / @inputs.period! - - while @state.phase >= 1 - @state.phase -= 1 - @state.count += 1 - @out\set @state.count - -every = ValueStream.meta - meta: - name: 'every' - summary: "Emit events regularly." - examples: { '(every [clock] period [evt])' } - description: "Emits `evt` as an event once every `period` seconds. - -- `clock` should be a `time/clock` event stream. This argument can be omitted - and the stream be passed as a dynamic definition in `*clock*` instead. -- `period` should be a num-value. -- `evt` can be a value of any type. It defaults to `bang`. -- the return type will be an event stream with the same type as `evt`." - value: class extends Op - new: (...) => - super ... - @state or= 0 - - pattern = -evt.clock + val.num + -val! - setup: (inputs, scope) => - { clock, period, evt } = pattern\match inputs - super - clock: Input.hot clock or scope\get '*clock*' - period: Input.cold period - evt: Input.cold evt or ValueStream 'bang', true - @out = EventStream @inputs.evt\type! - - tick: => - for tick in *@inputs.clock! - @state += tick.dt / @inputs.period! - - while @state >= 1 - @state -= 1 - @out\add @inputs.evt! - -sequence = ValueStream.meta - meta: - name: 'sequence' - summary: "Emit a sequence of values as events over time." - examples: { '(sequence [clock] delay0 evt1 delay1 evt2 delay2…)' } - description: " -Emits `evt1`, `evt2`, … as events with delays `delay0`, `delay1`, … in between. - -- `clock` should be a `time/clock` event stream. This argument can be omitted - and the stream be passed as a dynamic definition in `*clock*` instead. -- `delay0`, `delay1`, … must be num-values. -- `evt1`, `evt2`, … must be values of the same type. -- the return type will be an event stream with the same type as the `evt`s." - value: class extends Op - new: (...) => - super ... - @state or= { i: 1, t: 0 } - - pair = (val! + val.num)\named('value', 'delay') - pattern = -evt.clock + val.num + pair*0 - - inputify = (step) -> - { - delay: Input.cold step.delay - value: if step.value then Input.hot step.value - } - - setup: (inputs, scope) => - { clock, first, steps } = pattern\match inputs - @out = EventStream steps[1].value\type! - table.insert steps, 1, { delay: first } - super - clock: Input.hot clock or scope\get '*clock*' - steps: [inputify step for step in *steps] - - tick: => - for tick in *@inputs.clock! - @state.t += tick.dt - - change, current = false, nil - while true - current = @inputs.steps[@state.i] - if @state.t >= current.delay! - @state.t -= current.delay! - @state.i = 1 + (@state.i % #@inputs.steps) - change = true - else - break - - if current.value and (change or current.value\dirty!) - @out\add current.value! - -{ - :clock - 'scale-time': scale_time - :lfo - :ramp - :tick - :every - :sequence - '*clock*': with Clock 1/60 - .meta = - name: '*clock*' - summary: 'Default clock source (60fps).' -} diff --git a/lib/util.moon b/lib/util.moon deleted file mode 100644 index dc133e4..0000000 --- a/lib/util.moon +++ /dev/null @@ -1,112 +0,0 @@ -import Op, ValueStream, EventStream, Input, val, evt from require 'alv.base' - -all_same = (list) -> - for v in *list[2,] - if v != list[1] - return false - - list[1] - -switch_ = ValueStream.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 - val_or_evt = (val! / evt!)! - pattern = (val.num / val.bool) + val_or_evt*0 - setup: (inputs) => - { i, values } = pattern\match inputs - - @state = values[1].value.__class == ValueStream - - @out = if @state - ValueStream values[1]\type! - else - EventStream values[1]\type! - - super - i: Input.hot i - values: [Input.hot 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 @state - @out\set active and active! - else - if active and active\dirty! - for event in *active! - @out\add event - -edge = ValueStream.meta - meta: - name: 'edge' - summary: "Convert rising edges to bangs." - examples: { '(edge bool)' } - - value: class extends Op - setup: (inputs) => - @out or= EventStream 'bang' - value = val.bool\match inputs - super value: Input.hot value - - tick: => - now = @inputs.value! - if now and not @state.last - @out\set true - @state.last = now - -change = ValueStream.meta - meta: - name: 'change' - summary: "Convert value changes to events." - examples: { '(change val)' } - - value: class extends Op - setup: (inputs) => - value = val!\match inputs - @out or= EventStream value\type! - super value: Input.hot value - - tick: => - now = @inputs.value! - if now != @state - @out\add @inputs.value! - @state = now - -hold = ValueStream.meta - meta: - name: 'hold' - summary: "Convert events to value changes." - examples: { '(hold evt)' } - - value: class extends Op - setup: (inputs) => - event = evt!\match inputs - @out or= ValueStream event\type! - super event: Input.hot event - - tick: => - for val in *@inputs.event! - @out\set val - -{ - 'switch': switch_ - :edge - :change - :hold -} -- cgit v1.2.3