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 | |
| parent | Value -> Value/Event/IO-Stream (diff) | |
| download | alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.tar.gz alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.zip | |
new core.base.match, update lib
| -rw-r--r-- | core/base/init.moon | 10 | ||||
| -rw-r--r-- | core/base/match.moon | 368 | ||||
| -rw-r--r-- | core/base/op.moon | 18 | ||||
| -rw-r--r-- | core/builtin.moon | 7 | ||||
| -rw-r--r-- | core/result.moon | 12 | ||||
| -rw-r--r-- | core/stream/base.moon | 11 | ||||
| -rw-r--r-- | core/stream/event.moon | 12 | ||||
| -rw-r--r-- | core/stream/io.moon | 9 | ||||
| -rw-r--r-- | core/stream/value.moon | 14 | ||||
| -rw-r--r-- | core/version.moon | 4 | ||||
| -rw-r--r-- | extra/docs.moon | 4 | ||||
| -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 |
23 files changed, 807 insertions, 520 deletions
diff --git a/core/base/init.moon b/core/base/init.moon index 8e376fc..1b71086 100644 --- a/core/base/init.moon +++ b/core/base/init.moon @@ -1,14 +1,16 @@ ---- -- Base definitions for extensions. -- --- This module exports the following classes that extension modules may need: +-- This module exports the following classes and tables that extension modules +-- may need: -- -- @module base -- @see Op -- @see Action -- @see FnDef -- @see Input --- @see match +-- @see base.match.val +-- @see base.match.evt -- @see ValueStream -- @see EventStream -- @see IOStream @@ -19,7 +21,7 @@ import Op from require 'core.base.op' import Action from require 'core.base.action' import FnDef from require 'core.base.fndef' import Input from require 'core.base.input' -import match from require 'core.base.match' +import val, evt from require 'core.base.match' import ValueStream, EventStream, IOStream from require 'core.stream' import Result from require 'core.result' import Error from require 'core.error' @@ -29,7 +31,7 @@ import Error from require 'core.error' :Action :FnDef :Input - :match + :val, :evt -- redundant exports, to keep anything an extension might need in one import :ValueStream, :EventStream, :IOStream diff --git a/core/base/match.moon b/core/base/match.moon index 69d064d..b780ff6 100644 --- a/core/base/match.moon +++ b/core/base/match.moon @@ -1,95 +1,295 @@ ----- --- Utilities for matching `Result` types. +----- +--- Pattern capturing for Op argument parsing. -- --- @module match +-- There is only one basic buildings block for assembling patterns: +-- `Type`. It can match `ValueStream`s and `EventStream`s depending on its +-- metatype argument and can take an optional type name to match as an argument. +-- +-- In addition to this primitive, the following modifiers are available: +-- `Repeat`, `Sequence`, `Choice`, and `Optional`. They can be used directly, +-- but there is also a number of shorthands for assembling patterns quickly: +-- +-- - `val()` and `evt()`: Shorthands for `Type('value')` and `Type('event')` +-- - `val.num`: Shorthand for `Type('value', 'num')` +-- - `evt.str`: Shorthand for `Type('event', 'str')` +-- - `pat * 2`: Shorthand for `Repeat(pat, 1, 2)` (1-4 times `pat`) +-- - `pat * 0`: Shorthand for `Repeat(pat, 1, nil)` (1-* times `pat`) +-- - `pat ^ 2`: Shorthand for `Repeat(pat, 0, 2)` (0-4 times `pat`) +-- - `pat ^ 0`: Shorthand for `Repeat(pat, 0, nil)` (0-* times `pat`) +-- - `a + b + … + z`: Shorthand for `Sequence{ a, b, ..., z }` +-- - `a / b / … / z`: Shorthand for `Choice{ a, b, ..., z }` +-- - `-pat`: Shorthand for `Optional(pat)` +-- +-- To perform the actual matching, call the `:match` method on a pattern and +-- pass a sequence of `Result`s. The method will either return the captured +-- `Result`s (or a table structuring them) +-- +-- Any ambiguous pattern can be set to 'recall mode' by invoking it. +-- Recalling patterns will memorize the first Result they match, and +-- only match further Results of the same type. For example +-- +-- arg = (val.num / val.str)! +-- pattern = arg + arg +-- +-- ...will match either two numbers or two strings, but not one number and one +-- string. Recalling works on `Choice` and `Type` patterns. `Type` patterns +-- without a type (`val!` and `evt!`) always behave like this. +-- +-- On `Sequence` patterns, a special method `:named` exists. It takes a +-- sequence of keys that are used instead of integers when constructing the +-- capture table: +-- +-- pattern = (val.str + val.num):named{'key', 'value'} +-- pattern:match(...) +-- -- returns { {key='a', value=1}, {key='b', value=2}, ...} +-- +-- @module base.match import Error from require 'core.error' -unpack or= table.unpack +import ValueStream, EventStream from require 'core.stream' + +local Repeat, Sequence, Choice, Optional class Pattern - new: (opts) => - if 'string' == type opts - splat, const, type, opt = opts\match '^(%*?)(=?)([%w%-%_%/]+)(%??)$' - assert type, "couldn't parse type pattern '#{opts}'" - opts = { - :type - splat: splat == '*' - const: const == '=' - opt: opt == '?' - } - - @type = opts.type - @const = opts.const - @opt = opts.opt - @splat = opts.splat - - matches: (result) => - return false unless result - - if @const - return false unless result\is_const! - - if not result.value - return @type == 'nil' - - return true if @type == 'any' - - result.value.type == @type - - match: (results) => - if @splat - matched = while @matches results[1] - table.remove results, 1 - - assert @opt or #matched > 0, Error 'argument', "expected at least one argument for spread" - matched - else - result = results[1] - matches = @matches result - assert @opt or matches, Error 'argument', "argument #{result and result\type!} incompatible with expected type #{@}" - if matches then table.remove results, 1 + match: (seq) => + @reset! + num, cap = @capture seq, 1 + assert num == #seq, do + Error 'argument', "couldn't match arguments against pattern #{@}" + cap + + remember: (key) => + return true unless @recall + + @recalled or= key + @recalled == key + + rep: (min, max) => Repeat @, min, max + + reset: => @recalled = nil + + __call: => @ + __mul: (num) => Repeat @, 1, if num ~= 0 then num + __pow: (num) => Repeat @, 0, if num ~= 0 then num + __add: (other) => Sequence { @, other } + __div: (other) => Choice { @, other } + __unm: => Optional @ + + __inherited: (cls) => + cls.__base.__call or= @__call + cls.__base.__mul or= @__mul + cls.__base.__pow or= @__pow + cls.__base.__add or= @__add + cls.__base.__div or= @__div + cls.__base.__unm or= @__unm + +--- Base Stream Pattern. +-- +-- When instantiated with `type`, only succeeds for `Stream`s whose value and +-- meta types match. +-- +-- Otherwise, matches Streams based only on `metatype` for the first match, but +-- using both afterwards (recall mode). +-- +-- @function Stream +-- @tparam string metatype "value" or "event" +-- @tparam ?string type type name +class Type extends Pattern + new: (@metatype, @type) => + @recall = not @type + + capture: (seq, i) => + return unless seq[i] + type, mt = seq[i]\type!, seq[i]\metatype! + return unless @metatype == mt + match = if @type then type == @type else @remember type + if match + 1, seq[i] __tostring: => - str = @type - str = '*' .. str if @splat - str = '=' .. str if @const - str = str .. '?' if @opt + str = tostring @type + str ..= '!' if @metatype == 'event' str ---- match inputs to a argument type definition. --- --- `pattern` is a string of type entries. Every type entry can be like this: --- --- - `any` - matches one `Result` and returns it. --- - `typename` - matches one `Result` of type `typename` and returns it. --- - `=typename` - matches one eval-time const `Result`s of type `typname` and --- returns it. --- - `typename?` - matches what `typename` would match, if possible (greedy). --- Otherwise returns `nil`. --- - `*typename` - matches as many `typename` `Result`s as possible (greedy). --- Throws if there isn't at least one such `Result`. Returns a sequence of --- `Result`s. --- - `*typename?` - like `*typename`, except it also matches zero `Result`s. --- - `*=typename`, `=typename?` and `*=typename?` behave as expected. --- --- Except for `typename?` and `*typename?`, all entries throw if they cannot --- match the next `Result` in `inputs`. --- --- Throws if there are leftover `inputs` after matching all of `pattern`. --- --- @tparam string pattern the argument type definition --- @tparam {Result,...} inputs the list of inputs --- @treturn {Result|{Result,...},...} the inputs as matched against `pattern` -match = (pattern, inputs) -> - patterns = while pattern - pat, rest = pattern\match '^([^ ]+) (.*)$' - pat = pattern unless pat - pattern = rest - Pattern pat - values = [p\match inputs for p in *patterns] - assert #inputs == 0, Error 'argument', "#{#inputs} extra arguments given!" - values +--- Repeat a pattern. +-- +-- Matches a given `inner` pattern as many times as possible, within the given +-- minimum/maximum counts. Matching this pattern results in a sequence of the +-- individual captures produced by the inner pattern. +-- +-- @function Repeat +-- @tparam Pattern inner the original pattern +-- @tparam ?number min minimum amount of repetitions +-- @tparam ?number max maximum amount of repetitions (default infinite) +class Repeat extends Pattern + new: (@inner, @min, @max) => + + capture: (seq, i) => + take, all = 0, {} + while true + num, cap = @inner\capture seq, i+take + break unless num + + take += num + table.insert all, cap + + break if @max and take >= @max + + return if @min and take < @min + return if @max and take > @max + + take, all + + reset: => + @inner\reset! + + __call: => + @@ @inner!, @min, @max + + __tostring: => + min = @min or '0' + max = @max or '*' + "#{@inner}{#{min}-#{max}}" + +--- Match multiple patterns in order. +-- +-- Matches the inner patterns in order, only succeeds if all of them match. +-- Captures the individual captures produced by the inner patterns in a +-- sequence, or table with keys specified in `keys` or using the `:named(keys)` +-- modifier. +-- +-- @function Sequence +-- @tparam {Pattern,...} elements the inner patterns +-- @tparam ?{string,...} keys the keys to use when capturing matches +class Sequence extends Pattern + new: (@elements, @keys) => + + capture: (seq, i) => + take, all = 0, {} + for key, elem in ipairs @elements + num, cap = elem\capture seq, i+take + return unless num + + take += num + key = @keys[key] if @keys + all[key] = cap + + take, all + + reset: => + for elem in *@elements + elem\reset! + + named: (...) => + @@ [e for e in *@elements], { ... } + + __call: => + @@ [e! for e in *@elements] + + __add: (other) => + elements = [e for e in *@elements] + table.insert elements, other + @@ elements + + __tostring: => + core = table.concat [tostring e for e in *@elements], ' ' + "(#{core})" + +--- Match one of multiple options. +-- +-- Matches using the first matching pattern in `elements` and returns its +-- captured value. Supports recalling the matched subpattern. +-- +-- @function Choice +-- @tparam {Pattern,...} elements the inner patterns +-- @tparam ?{string,...} keys the keys to use when capturing matches +class Choice extends Pattern + new: (@elements, @recall=false) => + + capture: (seq, i) => + for key, elem in ipairs @elements + num, cap = elem\capture seq, i + if num and @remember key + return num, cap + + reset: => + super! + for elem in *@elements + elem\reset! + + __call: => + @@ [e! for e in *@elements], true + + __div: (other) => + elements = [e for e in *@elements] + table.insert elements, other + @@ elements + + __tostring: => + core = table.concat [tostring e for e in *@elements], ' | ' + "(#{core})" + +--- Optionally match a pattern. +-- +-- Matches using the first matching pattern in `elements` and returns its +-- captured value. Supports recalling the matched subpattern. +-- +-- @function Optional +-- @tparam {Pattern,...} elements the inner patterns +-- @tparam ?{string,...} keys the keys to use when capturing matches +class Optional extends Pattern + new: (@inner) => + + capture: (seq, i) => + num, cap = @inner\capture seq, i + num or 0, cap + + reset: => + @inner\reset! + + __call: => + @@ @inner! + + __unm: => @ + + __tostring: => "#{@inner}?" + +--- `Value` shorthands. +-- +-- Call or index with a string to obtain a `Type` instance. +-- Call to obtain a wildcard pattern. +-- +-- val.str, val.num +-- val['vec3'], val('vec3') +-- val() +-- +-- @table val +val = setmetatable {}, { + __index: (key) => + with v = Type 'value', key + @[key] = v + + __call: (...) => Type 'value', ... +} + +--- `Event` shorthands. +-- +-- Call or index with a string to obtain an `Type` instance. +-- Call to obtain a wildcard pattern. +-- +-- evt.bang, evt.str, evt.num +-- evt['midi/message'], evt('midi/message') +-- evt() +-- +-- @table evt +evt = setmetatable {}, { + __index: (key) => + with v = Type 'event', key + @[key] = v + + __call: (...) => Type 'event', ... +} { - :Pattern - :match + :Type, :Repeat, :Sequence, :Choice, :Optional + :val, :evt } diff --git a/core/base/op.moon b/core/base/op.moon index 4880afd..b0a83dc 100644 --- a/core/base/op.moon +++ b/core/base/op.moon @@ -5,7 +5,7 @@ deepcopy = (val) -> switch type val - when 'number', 'string', 'boolean' + when 'number', 'string', 'boolean', 'nil' val when 'table' assert (not getmetatable {}), "state should only contain simple tables!" @@ -35,8 +35,8 @@ class Op -- -- @treturn Op fork: => - out = if @out then @out\fork - state = if @state then deepcopy state + out = if @out then @out\fork! + state = if @state then deepcopy @state @@ out, state --- internal state of this Op. @@ -53,8 +53,8 @@ class Op -- `ValueStream`, it should have a value assigned via `set` or the -- constructor once `tick` is called the first time. If `out`'s value is not -- initialized in `new` or `setup`, the implementation must make sure - -- `tick``(true)` is called at -- least on the first eval-cycle the Op goes - -- through, e.g. by using an `Input.value`. + -- `tick``(true)` is called at least on the first eval-cycle the Op goes + -- through, e.g. by using an `Input.hot` with a `ValueStream`. -- -- @tfield Stream out @@ -95,10 +95,10 @@ class Op --- handle incoming events and update `out` (optional). -- - -- Called once per frame if any `Input`s are dirty. Some `Input`s (like - -- `Input.value`) have special behaviour immediately after `setup`, that can - -- cause them to become dirty at eval-time. In this case, an eval-time tick - -- is executed. You can detect this using the `setup` parameter. + -- Called once per frame if any `Input`s are dirty. Some `Input`s may have + -- special behaviour immediately after `setup` that can cause them to become + -- dirty at eval-time. In this case, an eval-time tick is executed. You can + -- detect this using the `setup` parameter. -- -- `tick` is called after `setup`. `tick` is not called immediately after -- `setup` if no `inputs` are dirty. Update `out` here. diff --git a/core/builtin.moon b/core/builtin.moon index f9087c4..1ba9293 100644 --- a/core/builtin.moon +++ b/core/builtin.moon @@ -5,7 +5,7 @@ -- documentation. -- -- @module builtin -import Action, Op, FnDef, Input, match from require 'core.base' +import Action, Op, FnDef, Input from require 'core.base' import ValueStream, LiteralValue from require 'core.stream.value' import Result from require 'core.result' import Cell from require 'core.cell' @@ -263,10 +263,9 @@ trace = ValueStream.meta value: class extends Action class traceOp extends Op setup: (inputs) => - { prefix, value } = match 'str any', inputs super - prefix: Input.cold prefix - value: Input.hot value + prefix: Input.cold inputs[1] + value: Input.hot inputs[2] tick: => L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" diff --git a/core/result.moon b/core/result.moon index cf228ec..6c994cb 100644 --- a/core/result.moon +++ b/core/result.moon @@ -12,17 +12,25 @@ class Result --- return whether this Result's value is const. is_const: => not next @side_inputs - --- assert value-constness and returns the value. + --- assert value-constness and return the value. -- @tparam[opt] string msg the error message to throw + -- @treturn any const: (msg) => assert not (next @side_inputs), msg or "eval-time const expected" @value - --- assert this result has a value, returns its type. + --- assert this result has a value, return its type. + -- @treturn string type: => assert @value, "Result with value expected" @value.type + --- assert this result has a value, returns its metatype. + -- @treturn string `"value"` or `"event"` + metatype: => + assert @value, "Result with value expected" + @value.metatype + --- create a copy of this result with value-copy semantics. -- the copy has the same @value and @side_inputs, but will not update -- anything on \tick. diff --git a/core/stream/base.moon b/core/stream/base.moon index ee4ee17..cdccdc2 100644 --- a/core/stream/base.moon +++ b/core/stream/base.moon @@ -53,17 +53,6 @@ class Stream -- -- @tfield ?table meta - __tostring: => - value = if @meta.name - @meta.name - else if 'table' == (type @value) and rawget @value, '__base' - @value.__name - else - tostring @value - "<#{@@__name} #{@type}: #{value}>" - - __inherited: (cls) => cls.__base.__tostring = @__tostring - --- static functions -- @section static diff --git a/core/stream/event.moon b/core/stream/event.moon index 5dbf846..f7b49c4 100644 --- a/core/stream/event.moon +++ b/core/stream/event.moon @@ -26,7 +26,7 @@ class EventStream extends Stream @events = {} @updated = registry.Registry.active!.tick - table.insert @events, {} + table.insert @events, event --- get the sequence of current events (if any). -- @@ -45,11 +45,19 @@ class EventStream extends Stream -- Used to wrap insulate eval-cycles from each other. -- -- @treturn EventStream - fork: => EventStream @type + fork: => @@ @type --- alias for `unwrap`. __call: (...) => @unwrap ... + __tostring: => + "<#{@@__name} #{@type}>" + + --- Stream metatype. + -- + -- @tfield string metatype + metatype: 'event' + --- the type name of the stream. -- -- the following builtin typenames are used: diff --git a/core/stream/io.moon b/core/stream/io.moon index fcaeb9f..e3e59a2 100644 --- a/core/stream/io.moon +++ b/core/stream/io.moon @@ -24,6 +24,13 @@ class IOStream extends EventStream -- @tparam string type the typename of this stream. new: (type) => super type + --- create a mutable copy of this stream. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn IOStream + fork: => @ + --- poll for changes. -- -- Called every frame by the main event loop to update internal state. @@ -32,7 +39,7 @@ class IOStream extends EventStream --- check whether this adapter requires processing. -- -- Must return a boolean indicating whether `Op`s that refer to this instance - -- via `Input.io` should be notified (via `Op:tick`). May be called multiple + -- via `Input.hot` should be notified (via `Op:tick`). May be called multiple -- times. May be called before `tick` on the first frame after construction. -- -- If this is not overrided, the `EventStream` interface can be used, see diff --git a/core/stream/value.moon b/core/stream/value.moon index 0220f06..ce3ffc7 100644 --- a/core/stream/value.moon +++ b/core/stream/value.moon @@ -57,6 +57,20 @@ class ValueStream extends Stream -- Compares two `ValueStream`s by comparing their types and their Lua values. __eq: (other) => other.type == @type and other.value == @value + __tostring: => + value = if @meta.name + @meta.name + else if 'table' == (type @value) and rawget @value, '__base' + @value.__name + else + tostring @value + "<#{@@__name} #{@type}: #{value}>" + + --- Stream metatype. + -- + -- @tfield string metatype + metatype: 'value' + --- the type name of this stream. -- -- the following builtin typenames are used: diff --git a/core/version.moon b/core/version.moon index af97600..41d4ab3 100644 --- a/core/version.moon +++ b/core/version.moon @@ -12,8 +12,8 @@ -- @tfield string web the repo web URL { tag: "v0.0" - rev_short: "662def4" - rev_long: "662def4ef082412147ae8126e80065d245f4b426" + rev_short: "677c0d2" + rev_long: "677c0d2f01e14bbeca1583ec7878d80c71c3aa68" repo: "https://github.com/s-ol/alivecoding.git" web: "https://github.com/s-ol/alivecoding/tree/v0.0" } diff --git a/extra/docs.moon b/extra/docs.moon index f43ecfa..19c272b 100644 --- a/extra/docs.moon +++ b/extra/docs.moon @@ -1,4 +1,4 @@ -import Value, Scope from require 'core' +import ValueStream, Scope from require 'core' import render, layout, autoref from require 'extra.layout' import section, h1, h2, h3, p, ul, li, a, code, r from require 'extra.dom' @@ -68,7 +68,7 @@ spit OUT, switch command p "These definitions are automatically loaded into the global Scope of every alive session." ul for key, val in opairs require 'core.builtin' - li render key, Value.wrap val + li render key, ValueStream.wrap val } } 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 } |
