diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-25 10:43:29 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-25 11:25:05 +0000 |
| commit | 7cb862f6f6079509dafd466fff83c719cb2fd89e (patch) | |
| tree | 843f92c4d4b2507e88a525b7c56c29d343958e5b /lib | |
| parent | Value -> Value/Event/IO-Stream (diff) | |
| download | alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.tar.gz alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.zip | |
new core.base.match, update lib
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/logic.moon | 49 | ||||
| -rw-r--r-- | lib/math.moon | 73 | ||||
| -rw-r--r-- | lib/midi.moon | 90 | ||||
| -rw-r--r-- | lib/midi/core.moon | 70 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 133 | ||||
| -rw-r--r-- | lib/osc.moon | 52 | ||||
| -rw-r--r-- | lib/pilot.moon | 39 | ||||
| -rw-r--r-- | lib/random.moon | 59 | ||||
| -rw-r--r-- | lib/sc.moon | 20 | ||||
| -rw-r--r-- | lib/string.moon | 12 | ||||
| -rw-r--r-- | lib/time.moon | 151 | ||||
| -rw-r--r-- | lib/util.moon | 110 |
12 files changed, 459 insertions, 399 deletions
diff --git a/lib/logic.moon b/lib/logic.moon index 157d190..62f1963 100644 --- a/lib/logic.moon +++ b/lib/logic.moon @@ -1,4 +1,4 @@ -import Op, Value, Input, Error, match from require 'core.base' +import Op, ValueStream, Input, Error, val from require 'core.base' all_same = (first, list) -> for v in *list @@ -15,13 +15,13 @@ tobool = (val) -> true class ReduceOp extends Op - new: => super 'bool' - + pattern = val! + val! * 0 setup: (inputs) => - { first, rest } = match "any *any", inputs + @out or= ValueStream 'bool' + { first, rest } = pattern\match inputs super - first: Input.value first - rest: [Input.value v for v in *rest] + first: Input.hot first + rest: [Input.hot v for v in *rest] tick: => { :first, :rest } = @unwrap_all! @@ -31,7 +31,7 @@ class ReduceOp extends Op @out\set accum -eq = Value.meta +eq = ValueStream.meta meta: name: 'eq' summary: "Check for equality." @@ -39,16 +39,16 @@ eq = Value.meta description: "`true` if the types and values of all arguments are equal." value: class extends Op - new: => super 'bool', false - + pattern = val! + val! * 0 setup: (inputs) => - { first, rest } = match "any *any", 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.value first - rest: [Input.value v for v in *rest] + first: Input.hot first + rest: [Input.hot v for v in *rest] } else {} @@ -68,8 +68,7 @@ eq = Value.meta @out\set equal - -not_eq = Value.meta +not_eq = ValueStream.meta meta: name: 'not-eq' summary: "Check for inequality." @@ -77,11 +76,10 @@ not_eq = Value.meta description: "`true` if types or values of any two arguments are different." value: class extends Op - new: => super 'bool' - setup: (inputs) => + @out or= ValueStream 'bool', false assert #inputs > 1, Error 'argument', "need at least two values" - super [Input.value v for v in *inputs] + super [Input.hot v for v in *inputs] tick: => if not @inputs[1] @@ -99,7 +97,7 @@ not_eq = Value.meta @out\set diff -and_ = Value.meta +and_ = ValueStream.meta meta: name: 'and' summary: "Logical AND." @@ -107,7 +105,7 @@ and_ = Value.meta value: class extends ReduceOp fn: (a, b) -> a and b -or_ = Value.meta +or_ = ValueStream.meta meta: name: 'or' summary: "Logical OR." @@ -115,7 +113,7 @@ or_ = Value.meta value: class extends ReduceOp fn: (a, b) -> a or b -not_ = Value.meta +not_ = ValueStream.meta meta: name: 'not' summary: "Logical NOT." @@ -126,11 +124,11 @@ not_ = Value.meta setup: (inputs) => { value } = match 'any', inputs - super value: Input.value value + super value: Input.hot value tick: => @out\set not tobool @inputs.value! -bool = Value.meta +bool = ValueStream.meta meta: name: 'bool' summary: "Cast value to bool." @@ -138,11 +136,10 @@ bool = Value.meta description: "`false` if a is `false`, `nil` or `0`, `true` otherwise." value: class extends Op - new: => super 'bool' - setup: (inputs) => - { value } = match 'any', inputs - super value: Input.value value + @out or= ValueStream 'bool' + { value } = val!\match inputs + super value: Input.hot value tick: => @out\set tobool @inputs\value! diff --git a/lib/math.moon b/lib/math.moon index 69d83bb..b2f4ce0 100644 --- a/lib/math.moon +++ b/lib/math.moon @@ -1,14 +1,14 @@ -import Op, Value, Error, Input, match from require 'core.base' +import Op, ValueStream, Error, Input, val from require 'core.base' unpack or= table.unpack class ReduceOp extends Op - new: => super 'num' - + pattern = val.num + val.num*0 setup: (inputs) => - { first, rest } = match 'num *num', inputs + @out or= ValueStream 'num' + { first, rest } = pattern\match inputs super - first: Input.value first - rest: [Input.value v for v in *rest] + first: Input.hot first + rest: [Input.hot v for v in *rest] tick: => { :first, :rest } = @unwrap_all! @@ -17,44 +17,39 @@ class ReduceOp extends Op accum = @.fn accum, val @out\set accum -func_op = (arity, func) -> +func_op = (func, pattern) -> 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] + @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) -> - _, arity = args\gsub ' ', ' ' - - Value.meta +func_def = (name, args, func, summary, pattern) -> + ValueStream.meta meta: :name :summary examples: { "(#{name} #{args})" } - value: func_op arity+1, func + value: func_op func, pattern or val.num\rep 1, 1 evenodd_op = (remainder) -> class extends Op - new: => super 'bool' - + pattern = val.num + -val.num setup: (inputs) => - { val, div } = match 'num num?', inputs + @out or= ValueStream 'bool' + { val, div } = pattern\match inputs super - val: Input.value val - div: Input.value div or Value.num 2 + val: Input.hot val + div: Input.hot div or ValueStream.num 2 tick: => { :val, :div } = @unwrap_all! @out\set (val % div) == remainder -add = Value.meta +add = ValueStream.meta meta: name: 'add' summary: "Add values." @@ -63,7 +58,7 @@ add = Value.meta value: class extends ReduceOp fn: (a, b) -> a + b -sub = Value.meta +sub = ValueStream.meta meta: name: 'sub' summary: "Subtract values." @@ -72,7 +67,7 @@ sub = Value.meta value: class extends ReduceOp fn: (a, b) -> a - b -mul = Value.meta +mul = ValueStream.meta meta: name: 'mul' summary: "Multiply values." @@ -80,7 +75,7 @@ mul = Value.meta value: class extends ReduceOp fn: (a, b) -> a * b -div = Value.meta +div = ValueStream.meta meta: name: 'div' summary: "Divide values." @@ -89,7 +84,7 @@ div = Value.meta value: class extends ReduceOp fn: (a, b) -> a / b -pow = Value.meta +pow = ValueStream.meta meta: name: 'pow' summary: "Raise to a power." @@ -98,7 +93,7 @@ pow = Value.meta value: class extends ReduceOp fn: (a, b) -> a ^ b -mod = Value.meta +mod = ValueStream.meta meta: name: 'mod' summary: 'Modulo operator.' @@ -106,7 +101,7 @@ mod = Value.meta description: "Calculate remainder of division by `div`." value: func_op 2, (a, b) -> a % b -even = Value.meta +even = ValueStream.meta meta: name: 'even' summary: 'Check whether val is even.' @@ -115,7 +110,7 @@ even = Value.meta `div` defaults to 2." value: evenodd_op 0 -odd = Value.meta +odd = ValueStream.meta meta: name: 'odd' summary: 'Check whether val is odd.' @@ -124,7 +119,7 @@ odd = Value.meta `div` defaults to 2." value: evenodd_op 1 -mix = Value.meta +mix = ValueStream.meta meta: name: 'mix' summary: 'Linearly interpolate.' @@ -132,7 +127,7 @@ mix = Value.meta 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 +min = ValueStream.meta meta: name: 'min' summary: "Find the minimum." @@ -140,7 +135,7 @@ min = Value.meta description: "Return the lowest of arguments." value: func_op '*', math.min -max = Value.meta +max = ValueStream.meta meta: name: 'max' summary: "Find the maximum." @@ -154,7 +149,7 @@ 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)." +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)." @@ -164,7 +159,7 @@ 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." +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." @@ -181,11 +176,11 @@ sqrt = func_def 'sqrt', 'val', math.sqrt, "Square root function." :mix :min, :max - pi: with Value.wrap math.pi + pi: with ValueStream.wrap math.pi .meta = summary: 'The pi constant.' - tau: with Value.wrap math.pi*2 + tau: with ValueStream.wrap math.pi*2 .meta = summary: 'The tau constant.' - huge: with Value.wrap math.huge + huge: with ValueStream.wrap math.huge .meta = summary: 'Positive infinity constant.' :sin, :cos, :tan diff --git a/lib/midi.moon b/lib/midi.moon index e978702..2f3a109 100644 --- a/lib/midi.moon +++ b/lib/midi.moon @@ -1,22 +1,21 @@ -import Value, Op, Input, match from require 'core.base' +import ValueStream, EventStream, Op, Input, val, evt from require 'core.base' import input, output, inout, apply_range from require 'lib.midi.core' -gate = Value.meta +gate = ValueStream.meta meta: name: 'gate' summary: "gate from note-on and note-off messages." - examples: { '(midi/gate port note [chan])' } + 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 + pattern = -evt['midi/port'] + val.num -val.num + setup: (inputs, scope) => + @out or= ValueStream 'bool' + { port, note, chan } = pattern\match inputs super - port: Input.io port - note: Input.value note - chan: Input.value chan or Value.num -1 + 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 @@ -25,47 +24,42 @@ gate = Value.meta @out\set false if port\dirty! - for msg in port!\receive! + 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 = Value.meta +trig = ValueStream.meta meta: name: 'trig' summary: "`bang`s from note-on messages." - examples: { '(midi/trig port note [chan])' } + 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 + pattern = -evt['midi/port'] + val.num -val.num + setup: (inputs, scope) => + @out or= EventStream 'bang' + { port, note, chan } = pattern\match inputs super - port: Input.io port - note: Input.value note - chan: Input.value chan or Value.num -1 + 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 - 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 + for msg in *port! + if msg.a == note! and (chan! == -1 or msg.chan == chan!) + if msg.status == 'note-on' + @out\add true -trig = Value.meta +cc = ValueStream.meta meta: - name: 'trig' + name: 'cc' summary: "`num` from cc-change messages." - examples: { '(midi/cc port cc [chan [range]])' } + examples: { '(midi/cc [port] cc [chan [range]])' } description: " `range` can be one of: - 'raw' [ 0 - 128[ @@ -76,28 +70,24 @@ trig = Value.meta - (num) [ 0 - num[" value: class extends Op - new: => - super 'num' - - setup: (inputs) => - { port, cc, chan, range } = match 'midi/port num num? any?', inputs + pattern = -evt['midi/port'] + val.num + -val.num + -val.num + setup: (inputs, scope) => + { port, cc, chan, range } = pattern\match 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' + 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' - if not @out\unwrap! - @out\set apply_range @inputs.range, 0 + @out or= ValueStream 'num', 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 + 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 diff --git a/lib/midi/core.moon b/lib/midi/core.moon index d1930eb..e520bbf 100644 --- a/lib/midi/core.moon +++ b/lib/midi/core.moon @@ -1,4 +1,4 @@ -import Value, IO, Op, Registry, Input, Error, match from require 'core.base' +import ValueStream, IOStream, Op, Input, Error, val from require 'core.base' import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' bit = do @@ -30,27 +30,23 @@ find_port = (Klass, name) -> \openport id -class MidiPort extends IO - new: (@inp, @out) => - @messages = {} - - tick: => - if @inp - @messages = while true - delta, bytes = @inp\getmessage! - break unless delta +class MidiPort extends IOStream + new: => super 'midi/port' - { status, a, b } = bytes - chan = band status, 0xf - status = MIDI[rshift status, 4] - { :status, :chan, :a, :b, port: @ } + setup: (inp, out) => + @inp = inp and find_port RtMidiIn, inp + @out = out and find_port RtMidiOut, out - dirty: => #@messages > 0 + tick: => + return unless @inp + while true + delta, bytes = @inp\getmessage! + break unless delta - receive: => - coroutine.wrap -> - for msg in *@messages - coroutine.yield msg + { 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" @@ -59,15 +55,15 @@ class MidiPort extends IO @out\sendmessage status, a, b class PortOp extends Op - new: => super 'midi/port' + new: (...) => + super ... + @out or= MidiPort! tick: (inp, out) => - if (inp and inp\dirty!) or (out and out\dirty!) - inp = inp and find_port RtMidiIn, inp! - out = out and find_port RtMidiOut, out! - @out\set MidiPort inp, out + { :inp, :out } = @unwrap_all! + @out\setup inp, out -input = Value.meta +input = ValueStream.meta meta: name: 'input' summary: "Create a MIDI input port." @@ -75,12 +71,10 @@ input = Value.meta value: class extends PortOp setup: (inputs) => - { name } = match 'str', inputs - super name: Input.value name - - tick: => super @inputs.name + name = val.str\match inputs + super inp: Input.hot name -output = Value.meta +output = ValueStream.meta meta: name: 'output' summary: "Create a MIDI output port." @@ -88,12 +82,10 @@ output = Value.meta value: class extends PortOp setup: (inputs) => - { name } = match 'str', inputs - super name: Input.value name + name = val.str\match inputs + super out: Input.hot name - tick: => super nil, @inputs.name - -inout = Value.meta +inout = ValueStream.meta meta: name: 'inout' summary: "Create a bidirectional MIDI port." @@ -101,12 +93,10 @@ inout = Value.meta value: class extends PortOp setup: (inputs) => - { inp, out } = match 'str str', inputs + { inp, out } = (val.str + val.str)\match inputs super - inp: Input.value inp - out: Input.value out - - tick: => super @inputs.inp, @inputs.out + inp: Input.hot inp + out: Input.hot out apply_range = (range, val) -> if range\type! == 'str' diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon index 37ccb82..1e33cc4 100644 --- a/lib/midi/launchctl.moon +++ b/lib/midi/launchctl.moon @@ -1,15 +1,15 @@ -import Value, Op, Input, match from require 'core.base' +import ValueStream, EventStream, Op, Input, val, evt from require 'core.base' import apply_range, bit from require 'lib.midi.core' import bor, lshift from bit unpack or= table.unpack color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4) -cc_seq = Value.meta +cc_seq = ValueStream.meta meta: name: 'cc-seq' summary: "MIDI CC-Sequencer." - examples: { '(launchctl/cc-seq port i start chan [steps [range]])' } + 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. @@ -23,22 +23,21 @@ range can be one of: - (num) [ 0 - num[" value: class extends Op - new: => super 'num' - - setup: (inputs) => - { port, i, start, - chan, steps, range } = match 'midi/port num num num num? any?', inputs + 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.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' + 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' - if not @out\unwrap! - @out\set apply_range @inputs.range, 0 + @state or= {} + @out or= ValueStream 'num', apply_range @inputs.range, 0 tick: => { :port, :i, :start, :chan, :steps, :range } = @inputs @@ -52,7 +51,7 @@ range can be one of: curr_i = i! % #@state if port\dirty! changed = false - for msg in port!\receive! + 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 @@ -62,29 +61,28 @@ range can be one of: else @out\set apply_range range, @state[curr_i+1] -gate_seq = Value.meta +gate_seq = ValueStream.meta meta: name: 'gate-seq' summary: "MIDI Gate-Sequencer." - examples: { '(launchctl/gate-seq port i start chan [steps])' } + examples: { '(launchctl/gate-seq [port] i start chan [steps])' } description: " -returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from +Send `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 + 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.io port - i: Input.value i - start: Input.value start - chan: Input.value chan - steps: Input.value steps or Value.num 8 + 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 ' ' @@ -96,8 +94,8 @@ returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from 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 + 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 @@ -111,7 +109,7 @@ returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from curr_i = i! % #@state if port\dirty! - for msg in port!\receive! + 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 @@ -126,7 +124,72 @@ returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from @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 + { - 'gate-seq': gate_seq 'cc-seq': cc_seq + 'gate-seq': gate_seq + 'trig-seq': trig_seq } diff --git a/lib/osc.moon b/lib/osc.moon index fe4eae4..2453940 100644 --- a/lib/osc.moon +++ b/lib/osc.moon @@ -1,23 +1,23 @@ -import Op, Value, Input, match from require 'core.base' +import Op, ValueStream, Input, val from require 'core.base' import pack from require 'osc' import dns, udp from require 'socket' unpack or= table.unpack -connect = Value.meta +connect = ValueStream.meta meta: name: 'connect' summary: "Create a UDP remote." examples: { '(osc/connect host port)' } value: class extends Op - new: => super 'udp/socket' - + pattern = val.str + val.num setup: (inputs) => - { host, port } = match 'str num', inputs + @out or= ValueStream 'udp/socket' + { host, port } = pattern\match inputs super - host: Input.value host - port: Input.value port + host: Input.hot host + port: Input.hot port tick: => { :host, :port } = @unwrap_all! @@ -26,44 +26,42 @@ connect = Value.meta @out\set with sock = udp! \setpeername ip, port -send = Value.meta +send = ValueStream.meta meta: name: 'send' summary: "Send a value via OSC." - examples: { '(osc/send socket path val)' } + examples: { '(osc/send [socket] path val)' } description: "sends a message only when `val` is dirty." value: class extends Op - setup: (inputs) => - { socket, path, value } = match 'udp/socket str any', inputs + pattern = -val['udp/socket'] + val.str + val! + setup: (inputs, scope) => + { socket, path, value } = pattern\match inputs super - socket: Input.cold socket + socket: Input.cold socket or scope\get '*sock*' path: Input.cold path - value: Input.value value + value: Input.hot 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 + { :socket, :path, :value } = @unwrap_all! + msg = pack path, if 'table' == type value then unpack value else value + socket\send msg -send_state = Value.meta +send_state = ValueStream.meta meta: name: 'send' summary: "Synchronize a value via OSC." - examples: { '(osc/send! socket path val)' } + examples: { '(osc/send! [socket] path val)' } description: "sends a message whenever any parameter is dirty." value: class extends Op - setup: (inputs) => - { socket, path, value } = match 'udp/socket str any', inputs + pattern = -val['udp/socket'] + val.str + val! + setup: (inputs, scope) => + { socket, path, value } = pattern\match inputs super - socket: Input.value socket - path: Input.value path - value: Input.value value + socket: Input.hot socket or scope\get '*sock*' + path: Input.hot path + value: Input.hot value tick: => { :socket, :path, :value } = @unwrap_all! diff --git a/lib/pilot.moon b/lib/pilot.moon index b851440..1d6b554 100644 --- a/lib/pilot.moon +++ b/lib/pilot.moon @@ -1,4 +1,4 @@ -import Op, Value, Input, Error, match from require 'core.base' +import Op, ValueStream, Input, val, evt from require 'core.base' import udp from require 'socket' local conn @@ -20,47 +20,49 @@ send = (...) -> conn or= udp! conn\sendto str, '127.0.0.1', 49161 -play = Value.meta +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 } = match 'bang *any', inputs - assert #args < 6, Error 'argument', "too many arguments!" + { trig, args } = pattern\match inputs super - trig: Input.event trig + trig: Input.hot trig args: [Input.cold a for a in *args] tick: => { :trig, :args } = @inputs - if trig\dirty! and trig! + for _ in *trig! send [a! for a in *@inputs.args] -play_ = Value.meta +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 } = match 'any any any *any', inputs - assert #args < 3, Error 'argument', "too many arguments!" + { chan, octv, note, args } = pattern\match inputs super chan: Input.cold chan octv: Input.cold octv - note: Input.event note + note: Input.hot 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 + { :chan, :octv, :note, :args } = @inputs + for note in *note! + send { chan!, octv!, note! }, args -effect = Value.meta +effect = ValueStream.meta meta: name: 'effect' summary: "Set effect parameters." @@ -68,12 +70,13 @@ effect = Value.meta 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 } = match 'str num num', inputs + { which, a, b } = pattern\match inputs super { - Input.cold which - Input.value a - Input.value b + Input.hot which + Input.hot a + Input.hot b } tick: => diff --git a/lib/random.moon b/lib/random.moon index 9a98702..49da2b9 100644 --- a/lib/random.moon +++ b/lib/random.moon @@ -1,16 +1,16 @@ -import Value, Error, Op, Input, match from require 'core.base' +import ValueStream, Error, Op, Input, val, evt from require 'core.base' apply_range = (range, val) -> if range\type! == 'str' switch range! - when 'uni' then val / 128 - when 'bip' then val / 64 - 1 - when 'rad' then val / 64 * math.pi - when 'deg' then val / 128 * 360 + 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 / 128 * range! + elseif range\type! == 'num' + val * range! else error Error 'argument', "range has to be a string or number" @@ -22,8 +22,9 @@ range can be one of: - 'deg' [ 0 - 360[ - (num) [ 0 - num[" -class num extends Op -num = Value.meta +pattern = -evt.bang + -(val.num / val.str) + +num = ValueStream.meta meta: name: 'num' summary: 'Generate a random number.' @@ -32,24 +33,25 @@ num = Value.meta #{range_doc}" value: class extends Op - new: => - super 'num' - @gen! + new: (...) => + super ... + @out or= ValueStream 'num' + @state or @gen! - gen: => @state = { math.random! } + gen: => @state = math.random! setup: (inputs) => - { trig, range } = match 'bang? any?', inputs + { trig, range } = pattern\match inputs super - trig: trig and Input.event trig - range: Input.value range or Value.str 'uni' + 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[1] + @out\set apply_range @inputs.range, @state -vec_ = (n) -> - Value.meta +vec = (n) -> + ValueStream.meta meta: name: "vec#{n}" summary: 'Generate a random vector.' @@ -58,17 +60,18 @@ vec_ = (n) -> #{range_doc}" value: class extends Op - new: => - super "vec#{n}" - @gen! + new: (...) => + super ... + @out or= ValueStream "vec#{n}" + @state or @gen! gen: => @state = for i=1,n do math.random! setup: (inputs) => - { trig, range } = match 'bang? any?', inputs + { trig, range } = pattern\match inputs super - trig: trig and Input.event trig - range: Input.value range or Value.str 'uni' + trig: trig and Input.hot trig + range: Input.hot range or ValueStream.str 'uni' tick: => @gen! if @inputs.trig and @inputs.trig\dirty! @@ -76,7 +79,7 @@ vec_ = (n) -> { :num - vec2: vec_ 2 - vec3: vec_ 3 - vec4: vec_ 4 + vec2: vec 2 + vec3: vec 3 + vec4: vec 4 } diff --git a/lib/sc.moon b/lib/sc.moon index dd9e826..5d68eee 100644 --- a/lib/sc.moon +++ b/lib/sc.moon @@ -1,8 +1,8 @@ -import Op, Value, Input, Error, match from require 'core.base' +import Op, ValueStream, Input, val, evt from require 'core.base' import pack from require 'osc' import dns, udp from require 'socket' -play = Value.meta +play = ValueStream.meta meta: name: 'play' summary: 'Play a SuperCollider SynthDef.' @@ -12,20 +12,20 @@ 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 + pattern = val['udp/socket'] + val.str + evt.bang + (val.str + val.num)\rep 0 setup: (inputs) => - { socket, synth, trig, ctrls } = match 'udp/socket str bang *any?', inputs + { socket, synth, trig, ctrls } = pattern\match 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" + flat_ctrls = {} + for { key, value } in *ctrls + table.insert flat_ctrls, key + table.insert flat_ctrls, value super - trig: Input.event trig + trig: Input.hot trig socket: Input.cold socket synth: Input.cold synth - ctrls: [Input.cold v for v in *ctrls] + ctrls: [Input.cold v for v in *flat_ctrls] tick: => if @inputs.trig\dirty! and @inputs.trig! diff --git a/lib/string.moon b/lib/string.moon index 8b48977..3f9b28b 100644 --- a/lib/string.moon +++ b/lib/string.moon @@ -1,15 +1,17 @@ -import Op, Value, Input from require 'core.base' +import Op, ValueStream, Input from require 'core.base' -str = Value.meta +str = ValueStream.meta meta: name: 'str' summary: "Concatenate/stringify values." examples: { '(.. v1 [v2…])', '(str v1 [v2…])' } value: class extends Op - new: => super 'str' + setup: (inputs) => + @out or= ValueStream 'string' + super [Input.hot 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] + tick: => + @out\set table.concat [tostring v! for v in *@inputs] { :str, '..': str diff --git a/lib/time.moon b/lib/time.moon index ea0e80a..28a0824 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -1,8 +1,13 @@ -import Value, Error, IO, Op, Input, match from require 'core.base' +import + ValueStream, EventStream, IOStream, + Error, Op, Input, val, evt +from require 'core.base' import monotime from require 'system' -class Clock extends IO +class Clock extends IOStream new: (@frametime) => + super 'clock' + return unless monotime @last = monotime! @dt = 0 @@ -17,9 +22,14 @@ class Clock extends IO else false + unwrap: => + dt = @dt + time = @last + { :dt, :time } + dirty: => @is_dirty -clock = Value.meta +clock = ValueStream.meta meta: name: 'clock' summary: "Create a clock source." @@ -28,21 +38,23 @@ clock = Value.meta IO that triggers other operators at a fixed frame rate. `fps` defaults to 60 and has to be an eval-time constant" value: class extends Op - new: => super 'clock' + new: (...) => + super ... + @out or= Clock! setup: (inputs) => - { fps } = match 'num?', inputs - super fps: Input.value fps or Value.num 60 + fps = (-val.num)\match inputs + super fps: Input.hot fps or ValueStream.num 60 + @out.frametime = 1 / @inputs.fps! tick: => - if @inputs.fps\dirty! - @out\set Clock 1 / @inputs.fps! + @out.frametime = 1 / @inputs.fps! -lfo = Value.meta +lfo = ValueStream.meta meta: name: 'lfo' summary: "Low-frequency oscillator." - examples: { '(lfo [clock] freq wave)' } + examples: { '(lfo [clock] freq [wave])' } description: " oscillates between 0 and 1 at the frequency freq. wave selects the wave shape from the following: @@ -50,31 +62,32 @@ wave selects the wave shape from the following: - `'saw'` - `'tri'`" value: class extends Op - new: => - super 'num' - @state.phase or= 0 + new: (...) => + super ... + @state or= 0 + @out or= ValueStream 'num' - default_wave = Value.str 'sin' + default_wave = ValueStream.str 'sin' + pattern = -evt.clock + val.num + -val.str setup: (inputs, scope) => - { clock, freq, wave } = match 'clock? num any?', inputs + { clock, freq, wave } = pattern\match inputs super - clock: Input.io clock or scope\get '*clock*' - freq: Input.value freq - wave: Input.value wave or default_wave + clock: Input.hot clock or scope\get '*clock*' + freq: Input.cold freq + wave: Input.hot wave or default_wave tau = math.pi * 2 tick: => if @inputs.clock\dirty! - { :clock, :freq, :wave } = @unwrap_all! + @state += @inputs.clock!.dt * @iputs.freq! - @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 Error 'argument', "unknown wave type '#{wave}'" + @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 = Value.meta +ramp = ValueStream.meta meta: name: 'ramp' summary: "Sawtooth LFO." @@ -82,31 +95,32 @@ ramp = Value.meta description: " ramps from 0 to max (default same as ramp) once every period seconds." value: class extends Op - new: => - super 'num' - @state.phase or= 0 + new: (...) => + super ... + @state or= 0 + @out or= ValueStream 'num' + pattern = -evt.clock + val.num + -val.num setup: (inputs, scope) => - { clock, period, max } = match 'clock? num num?', inputs + { clock, period, max } = pattern\match inputs super - clock: Input.io clock or scope\get '*clock*' - period: Input.value period - max: max and Input.value max + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period + max: max and Input.cold max tick: => clock_dirty = @inputs.clock\dirty! if clock_dirty - { :clock, :period, :max } = @unwrap_all! - max or= period - @state.phase += clock.dt / period + period = @inputs.period! + max = (@inputs.max or @inputs.period)! + @phase += @inputs.clock!.dt / period - while @state.phase >= 1 - @state.phase -= 1 + while @phase >= 1 + @phase -= 1 - if clock_dirty or (@inputs.max and @inputs.max\dirty!) - @out\set @state.phase * max + @out\set @phase * max -tick = Value.meta +tick = ValueStream.meta meta: name: 'tick' summary: "Count ticks." @@ -115,52 +129,51 @@ tick = Value.meta 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 + new: (...) => + super ... + @state or= { phase: 0, count: 0 } + @out or= ValueStream 'num', @state.count + pattern = -evt.clock + val.num setup: (inputs, scope) => - { clock, period } = match 'clock? num', inputs + { clock, period } = pattern\match inputs super - clock: Input.io clock or scope\get '*clock*' - period: Input.value period + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period tick: => - if @inputs.clock\dirty! - { :clock, :period, :max } = @unwrap_all! - @state.phase += clock.dt / period + @state.phase += @inputs.clock!.dt / @inputs.period! - while @state.phase >= 1 - @state.phase -= 1 - @state.count += 1 - @out\set @state.count + while @state.phase >= 1 + @state.phase -= 1 + @state.count += 1 + @out\set @state.count -every = Value.meta +every = ValueStream.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 + new: (...) => + super ... + @state or= 0 + @out or= EventStream 'bang' + pattern = -evt.clock + val.num setup: (inputs, scope) => - { clock, period } = match 'clock? num', inputs + { clock, period } = pattern\match inputs super - clock: Input.io clock or scope\get '*clock*' - period: Input.value period + clock: Input.hot clock or scope\get '*clock*' + period: Input.cold period tick: => - if @inputs.clock\dirty! - { :clock, :period, :max } = @unwrap_all! - @state.phase += clock.dt / period + @state += @inputs.clock!.dt / @inputs.period! - while @state.phase >= 1 - @state.phase -= 1 - @out\set true + while @state >= 1 + @state -= 1 + @out\add true { :clock @@ -168,7 +181,7 @@ every = Value.meta :ramp :tick :every - '*clock*': with Value 'clock', Clock 1/60 + '*clock*': with Clock 1/60 .meta = name: '*clock*' summary: 'Default clock source (60fps).' diff --git a/lib/util.moon b/lib/util.moon index a2c65ba..d760a64 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -1,4 +1,4 @@ -import Op, Value, Input, Error, match from require 'core.base' +import Op, ValueStream, EventStream, Input, val, evt from require 'core.base' all_same = (list) -> for v in *list[2,] @@ -7,7 +7,7 @@ all_same = (list) -> list[1] -switch_ = Value.meta +switch_ = ValueStream.meta meta: name: 'switch' summary: "Switch between multiple inputs." @@ -19,17 +19,21 @@ switch_ = Value.meta (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 } = match 'any *any', inputs + { i, values } = pattern\match 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 + @state = values[1].value.__class == ValueStream + + @out = if @state + ValueStream values[1]\type! + else + EventStream values[1]\type! super - i: Input.value i - values: [Input.value v for v in *values] + i: Input.hot i + values: [Input.hot v for v in *values] tick: => { :i, :values } = @inputs @@ -41,66 +45,68 @@ switch_ = Value.meta else i = 1 + (math.floor i!) % #values values[i] - @out\set active and active! - -route = Value.meta + 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: '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." + name: 'edge' + summary: "Convert rising edges to bangs." + examples: { '(edge bool)' } value: class extends Op setup: (inputs) => - { i, values } = match 'any *any', 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 - 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 +change = ValueStream.meta + meta: + name: 'change' + summary: "Convert value changes to events." + examples: { '(change val)' } - super - i: Input.value i - values: [Input.value v for v in *values] + value: class extends Op + setup: (inputs) => + value = val!\match inputs + @out or= EventStream value\type! + super value: Input.hot value 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! + now = @inputs.value! + if now != @state + @out\add @inputs.value! + @state = now -route = Value.meta +hold = ValueStream.meta meta: - name: 'edge' - summary: "Convert rising edges to bangs." - examples: { '(edge bool)' } + name: 'hold' + summary: "Convert events to value changes." + examples: { '(hold evt)' } value: class extends Op - new: => super 'bang' - setup: (inputs) => - { value } = match 'bool', inputs - super value: Input.value value + event = evt!\match inputs + @out or= ValueStream event\type! + super event: Input.hot event tick: => - now = @inputs.value! - if now and not @state.last - @out\set true - @state.last = now + for val in *@inputs.event! + @out\set val { 'switch': switch_ - :route :edge + :change + :hold } |
