aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-02-16 11:47:24 +0000
committers-ol <s-ol@users.noreply.github.com>2020-02-16 11:47:24 +0000
commit2953f1e56b408fc26eb54fa65935505dd128ce82 (patch)
tree8c7c513ccf03df06c4e9e2e70ba68031d2d0b7a5
parentadd ==, mod (diff)
downloadalive-2953f1e56b408fc26eb54fa65935505dd128ce82.tar.gz
alive-2953f1e56b408fc26eb54fa65935505dd128ce82.zip
major refactoring: Const, Stream + ResultNode
-rw-r--r--core/base.moon69
-rw-r--r--core/builtin.moon64
-rw-r--r--core/cell.moon28
-rw-r--r--core/const.moon136
-rw-r--r--core/init.moon8
-rw-r--r--core/invoke.moon50
-rw-r--r--core/parsing.moon15
-rw-r--r--core/registry.moon5
-rw-r--r--core/scope.moon8
-rw-r--r--core/value.moon157
-rw-r--r--lib/debug.moon6
-rw-r--r--lib/envelope.moon13
-rw-r--r--lib/gui.moon9
-rw-r--r--lib/logic.moon46
-rw-r--r--lib/math.moon66
-rw-r--r--lib/midi/core.moon17
-rw-r--r--lib/midi/init.moon44
-rw-r--r--lib/midi/launchctl.moon99
-rw-r--r--lib/osc.moon11
-rw-r--r--lib/pilot.moon22
-rw-r--r--lib/string.moon9
-rw-r--r--lib/time.moon57
-rw-r--r--lib/util.moon53
23 files changed, 559 insertions, 433 deletions
diff --git a/core/base.moon b/core/base.moon
index dcf600a..1b1be5d 100644
--- a/core/base.moon
+++ b/core/base.moon
@@ -1,15 +1,13 @@
-class Op
--- common
- new: =>
- -- (...) => @setup ...
+-- base definitions for extensions
- get: => @value
- getc: =>
- L\warn "stream #{@} cast to constant"
- @value
+-- a persistent expression Operator
+--
+-- accepts Const or Stream inputs and produces a Stream output
+class Op
+ update: (dt) =>
--- Value interface
- update: =>
+ -- set @out to a Value (Const or Stream)
+ setup: (...) =>
destroy: =>
@@ -17,11 +15,16 @@ class Op
__tostring: => "<op: #{@@__name}>"
__inherited: (cls) => cls.__base.__tostring = @__tostring
- spawn: (Opdef, ...) ->
- Opdef ...
+-- a builtin / special form / cell-evaluation strategy
+--
+-- responsible for quoting/evaluating subexpressions,
+-- instantiating and patching Ops,
+-- updating the current Scope,
+-- etc.
class Action
--- common
+ -- head: the (:eval'd) head of the Cell to evaluate (a Const)
+ -- tag: the Tag of the expression to evaluate
new: (head, @tag) =>
@patch head
@@ -35,9 +38,9 @@ class Action
-- free resources
destroy: =>
- -- update this instance for :eavl() with new head
- -- if :patch() returns false, this instance is :destroy'ed and recreated instead
- -- must *not* return false when called after :new()
+ -- update this instance for :eval() with new head
+ -- if :patch() returns false, this instance is :destroy'ed and recreated
+ -- instead must *not* return false when called after :new()
-- only considered if Action types match
patch: (head) =>
if head == @head
@@ -46,35 +49,51 @@ class Action
@head = head
-- static
- @get_or_create: (ActionType, head, tag) ->
+ -- find & patch the action for the expression with Tag 'tag' if it exists,
+ -- and is compatible with the new Cell contents, otherwise instantiate it.
+ -- register the action with the tag, evaluate it and return the ResultNode
+ @eval_cell: (scope, tag, head, tail) =>
last = tag\last!
compatible = last and
- (last.__class == ActionType) and
+ (last.__class == @) and
(last\patch head) and
last
L\trace if compatible
- "reusing #{last} for #{tag} <#{ActionType.__name} #{head}>"
+ "reusing #{last} for #{tag} <#{@__name} #{head}>"
else if last
- "replacing #{last} with new #{tag} <#{ActionType.__name} #{head}>"
+ "replacing #{last} with new #{tag} <#{@__name} #{head}>"
else
- "initializing #{tag} <#{ActionType.__name} #{head}>"
+ "initializing #{tag} <#{@__name} #{head}>"
- if compatible
+ action = if compatible
tag\keep compatible
compatible
else
last\destroy! if last
- with next = ActionType head, tag
+ with next = @ head, tag
tag\replace next
+ action\eval scope, tail
+
__tostring: => "<#{@@__name} #{@head}>"
__inherited: (cls) => cls.__base.__tostring = @__tostring
+-- a ALV function definition
+--
+-- when called, expands its body with params bound to the fn arguments
+-- (see core.invoke.fn-invoke)
class FnDef
+ -- params: sequence of (:quote'd) symbols, each naming a function parameter
+ -- body: (:quote'd) expression the function evaluates to
+ -- scoe: the lexical scope the function was defined in (closure)
new: (@params, @body, @scope) =>
__tostring: =>
- table.concat [p\stringify! for p in *@params], ' '
+ "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
-:Op, :Action, :FnDef
+{
+ :Op
+ :Action
+ :FnDef
+}
diff --git a/core/builtin.moon b/core/builtin.moon
index 97f4457..71beac3 100644
--- a/core/builtin.moon
+++ b/core/builtin.moon
@@ -1,8 +1,8 @@
+-- builtin special forms
import Action, FnDef from require 'core.base'
-import Const from require 'core.const'
+import ResultNode, Value, Const from require 'core.value'
import Cell from require 'core.cell'
import Scope from require 'core.scope'
-import UpdateChildren from require 'core.invoke'
class doc extends Action
@doc: "(doc sym) - print documentation in console
@@ -13,8 +13,9 @@ prints the docstring for sym in the console"
assert #tail == 1, "'doc' takes exactly one parameter"
def = L\push tail[1]\eval, scope
- L\print "(doc #{tail[1]\stringify!}):\n#{def\getc!.doc}\n"
- nil
+ with ResultNode children: { def }
+ def = def.value\const!\unwrap!
+ L\print "(doc #{tail[1]\stringify!}):\n#{def.doc}\n"
class def extends Action
@doc: "(def sym1 val-expr1
@@ -28,16 +29,15 @@ updates all val-exprs."
assert #tail > 1, "'def' requires at least 2 arguments"
assert #tail % 2 == 0, "'def' requires an even number of arguments"
- values = L\push ->
+ children = L\push ->
return for i=1,#tail,2
name, val_expr = tail[i], tail[i+1]
- name = (name\quote scope)\getc 'sym'
+ name = (name\quote scope)\unwrap 'sym'
- val = val_expr\eval scope
- scope\set name, Const.wrap_ref val
- val
+ with val_expr\eval scope
+ scope\set name, .value
- UpdateChildren values
+ ResultNode :children
class use extends Action
@doc: "(use scope1 [scope2]...) - merge scopes into parent scope
@@ -48,12 +48,11 @@ all scopes have to be eval-time constants."
eval: (scope, tail) =>
L\trace "evaling #{@}"
for child in *tail
- value = L\push child\eval, scope
- L\trace @, "merging #{value} into #{scope}"
- assert value.type == 'scope', "'use' only works on scopes"
- scope\use value\getc 'scope'
+ result = L\push child\eval, scope
+ value = result\value_only!\const!
+ scope\use value\unwrap 'scope', "'use' only works on scopes"
- nil
+ ResultNode!
class require_ extends Action
@doc: "(require name-str) - require a module
@@ -65,10 +64,11 @@ name-str has to be an eval-time constant."
L\trace "evaling #{@}"
assert #tail == 1, "'require' takes exactly one parameter"
- name = L\push tail[1]\eval, scope
+ result = L\push tail[1]\eval, scope
+ name = result\value_only!\const!
L\trace @, "loading module #{name}"
- Const.wrap require "lib.#{name\getc 'str'}"
+ ResultNode value: Value.wrap require "lib.#{name\unwrap 'str'}"
class import_ extends Action
@doc: "(import sym1 [sym2]...) - require and define modules
@@ -79,12 +79,11 @@ requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the curr
L\trace "evaling #{@}"
assert #tail > 0, "'import' requires at least one arguments"
-
for child in *tail
- name = (child\quote scope)\getc 'sym'
- scope\set name, Const.wrap require "lib.#{name}"
+ name = (child\quote scope)\unwrap 'sym'
+ scope\set name, Value.wrap require "lib.#{name}"
- nil
+ ResultNode!
class import_star extends Action
@doc: "(import* sym1 [sym2]...) - require and use modules
@@ -97,10 +96,10 @@ requires modules sym1, sym2, ... and merges them into the current scope"
for child in *tail
- name = (child\quote scope)\getc 'sym'
- scope\use (Const.wrap require "lib.#{name}")\getc 'scope'
+ name = (child\quote scope)\unwrap 'sym'
+ scope\use (Value.wrap require "lib.#{name}")\unwrap 'scope'
- nil
+ ResultNode!
class fn extends Action
@doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function
@@ -118,7 +117,7 @@ the symbols p1, p2, ... will resolve to the arguments passed to the function."
param\quote scope
body = body\quote scope
- Const.wrap FnDef param_symbols, body, scope
+ ResultNode value: Value.wrap FnDef param_symbols, body, scope
class defn extends Action
@doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function
@@ -130,7 +129,7 @@ declares a lambda (see (doc fn)) and defines it in the current scope"
assert #tail == 3, "'defn' takes exactly three arguments"
{ name, params, body } = tail
- name = (name\quote scope)\getc 'sym'
+ name = (name\quote scope)\unwrap 'sym'
assert params.__class == Cell, "'defn's second argument has to be an expression"
param_symbols = for param in *params.children
assert param.type == 'sym', "function parameter declaration has to be a symbol"
@@ -139,9 +138,8 @@ declares a lambda (see (doc fn)) and defines it in the current scope"
body = body\quote scope
fn = FnDef param_symbols, body, scope
- scope\set name, Const.wrap fn
-
- nil
+ scope\set name, Value.wrap fn
+ ResultNode!
class do_expr extends Action
@doc: "(do expr1 [expr2]...) - update multiple expressions
@@ -150,7 +148,7 @@ evaluates and continously updates expr1, expr2, ...
the last expression's value is returned."
eval: (scope, tail) =>
- UpdateChildren [(expr\eval scope) or Const.empty! for expr in *tail]
+ ResultNode children: [expr\eval scope for expr in *tail]
class if_ extends Action
@doc: "(if bool then-expr [else-xpr]) - make an eval-time const choice
@@ -166,7 +164,7 @@ to then-expr, otherwise it is equivalent to else-xpr if given, or nil otherwise.
{ xif, xthen, xelse } = tail
xif = L\push xif\eval, scope
- xif = xif\getc!
+ xif = xif\value_only!\const!\unwrap!
if xif
xthen\eval scope
@@ -180,8 +178,8 @@ class trace extends Action
L\trace "evaling #{@}"
assert #tail == 1, "'trace' takes exactly one parameter"
- with val = L\push tail[1]\eval, scope
- L\print "trace:", val
+ with result = L\push tail[1]\eval, scope
+ L\print "trace #{tail[1]\stringify!}: #{result.value}"
{
:doc, :trace
diff --git a/core/cell.moon b/core/cell.moon
index 4aa847b..642e1a6 100644
--- a/core/cell.moon
+++ b/core/cell.moon
@@ -1,15 +1,19 @@
-import Const from require 'core.const'
+-- ALV Cell type
+import Const from require 'core.value'
import op_invoke, fn_invoke from require 'core.invoke'
import Tag from require 'core.tag'
+-- ALV Cell type
class Cell
-- common
- new: (@tag, @children, @white) =>
+ -- tag: the parsed Tag
+ -- children: sequence of child AST Nodes
+ -- white: optional sequence of whitespace segments ([0 .. #@children])
+ new: (@tag=Tag.blank!, @children, @white) =>
if not @white
@white = ['' for i=1,#@children+1]
- if not @tag
- @tag = Tag.blank!
+ assert #@white == #@children, "mismatched whitespace length"
head: => @children[1]
tail: => [c for c in *@children[2,]]
@@ -18,7 +22,8 @@ class Cell
-- AST interface
eval: (scope) =>
- head = @head!\eval scope
+ head_result = @head!\eval scope
+ head = head_result.value\const!
Action = switch head.type
when 'opdef'
-- scope\get 'op-invoke'
@@ -27,7 +32,7 @@ class Cell
-- scope\get 'fn-invoke'
fn_invoke
when 'builtin'
- head\getc!
+ head\unwrap!
else
print head
for k,v in pairs head
@@ -35,8 +40,7 @@ class Cell
print head.__class.__name
error "cannot evaluate expr with head #{head}"
- action = Action\get_or_create head, @tag
- action\eval scope, @tail!
+ Action\eval_cell scope, @tag, head, @tail!
quote: (scope) =>
children = [child\quote scope for child in *@children]
@@ -81,6 +85,9 @@ class Cell
tag, children, white = parse_args ...
@ tag, children, white
+-- A parenthesis-less Cell (root of an ALV document)
+--
+-- evaluates with an implicit 'do' in the head
class RootCell extends Cell
head: => Const.sym 'do'
tail: => @children
@@ -98,4 +105,7 @@ class RootCell extends Cell
@parse: (...) =>
@__parent.parse @, (Tag\parse '0'), ...
-:Cell, :RootCell
+{
+ :Cell
+ :RootCell
+}
diff --git a/core/const.moon b/core/const.moon
deleted file mode 100644
index 99e4178..0000000
--- a/core/const.moon
+++ /dev/null
@@ -1,136 +0,0 @@
-import Op, Action, FnDef from require 'core.base'
-
-local Scope
-load_ = ->
- import Scope from require 'core.scope'
-
-ancestor = (klass) ->
- assert klass, "cant find the ancestor of nil"
- while klass.__parent
- klass = klass.__parent
- klass
-
-class Ref
- new: (@original) =>
-
- get: (...) => @original\get ...
- getc: (...) => @original\get ...
-
- destroy: =>
- update: =>
-
-class Const
- types = {
- sym: true
- str: true
- num: true
- bool: true
- scope: true
- op: true
- opdef: true
- fndef: true
- builtin: true
- }
-
- new: (@type, @value, @raw) =>
- assert types[@type], "invalid Const type: #{@type}"
-
--- Value interface
- get: (type) =>
- assert not type or type == @type, "#{@} is not a #{type}"
- @value
-
- getc: (type) =>
- assert not type or type == @type, "#{@} is not a #{type}"
- @value
-
- update: (dt) =>
- switch @type
- when 'op'
- @value\update dt
-
--- AST interface
- eval: (scope) =>
- switch @type
- when 'num', 'str'
- @
- when 'sym'
- assert (scope\get @value), "undefined reference to symbol '#{@value}'"
- else
- error "cannot evaluate #{@}"
-
- quote: => @
-
- stringify: => @raw
-
- clone: (prefix) => @
- -- in case of doubt:
- -- clone: (prefix) => Const @type, @value, @raw
-
--- static
- __tostring: =>
- value = if @type == 'opdef' or @type == 'builtin' then @value.__name else @value
- "<#{@type}: #{value}>"
-
- __eq: (other) =>
- other.type == @type and other.value == @value
-
- unescape = (str) -> str\gsub '\\([\'"\\])', '%1'
-
- @parse: (type, sep) =>
- switch type
- when 'num' then (match) -> @ 'num', (tonumber match), match
- when 'sym' then (match) -> @ 'sym', match, match
- when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep
-
- @num: (num) -> Const 'num', num, tostring num
- @str: (str) -> Const 'str', str, "'#{str}'"
- @sym: (sym) -> Const 'sym', sym, sym
- @bool: (bool) -> Const 'bool', bool, tostring bool
- @empty: -> Const 'str', '', "''"
-
- @wrap: (val, name='(unknown)') ->
- typ = switch type val
- when 'number' then 'num'
- when 'string' then 'str'
- when 'table'
- if base = rawget val, '__base'
- -- a class
- switch ancestor val
- when Op then 'opdef'
- when Action then 'builtin'
- else
- error "#{name}: cannot wrap class '#{val.__name}'"
- elseif val.__class
- -- an instance
- switch ancestor val.__class
- when Op then 'op'
- when Scope then 'scope'
- when FnDef then 'fndef'
- when Const
- return val
- else
- error "#{name}: cannot wrap '#{val.__class.__name}' instance"
- else
- -- plain table
- return Const 'scope', Scope.from_table val
- else
- error "#{name}: cannot wrap Lua type '#{type val}'"
-
- Const typ, val
-
- @wrap_ref: (val) ->
- if base = rawget val, '__base'
- -- a class
- error "#{name}: cannot wrap_ref class '#{val.__name}'"
- elseif val.__class
- -- an instance
- switch ancestor val.__class
- when Op then Ref val
- when Const then val
- else
- error "#{name}: cannot wrap_ref '#{val.__class.__name}' instance"
- else
- error "#{name}: cannot wrap_ref Lua type '#{type val}'"
-
-:Const, :load_
diff --git a/core/init.moon b/core/init.moon
index 1d9e191..b885747 100644
--- a/core/init.moon
+++ b/core/init.moon
@@ -2,7 +2,7 @@ L or= setmetatable {}, __index: => ->
import Op, Action, FnDef from require 'core.base'
-import Const, load_ from require 'core.const'
+import Stream, Const, load_ from require 'core.value'
import Scope from require 'core.scope'
load_!
@@ -15,7 +15,8 @@ import cell, program from require 'core.parsing'
globals = Scope.from_table require 'core.builtin'
{
- :Const, :Cell, :RootCell
+ :Stream, :Const
+ :Cell, :RootCell
:Op, :Action, :FnDef
:Scope
@@ -28,5 +29,6 @@ globals = Scope.from_table require 'core.builtin'
scope\use inject if inject
ast = assert (cell\match str), "failed to parse: #{str}"
- Const.wrap ast\eval scope
+ result = ast\eval scope
+ result\value_only!
}
diff --git a/core/invoke.moon b/core/invoke.moon
index 5ec4a82..5a6c420 100644
--- a/core/invoke.moon
+++ b/core/invoke.moon
@@ -1,39 +1,23 @@
-import Const from require 'core.const'
+import ResultNode, Value from require 'core.value'
import Action from require 'core.base'
import Scope from require 'core.scope'
-class UpdateChildren
- new: (@children) =>
-
- update: (dt) =>
- for child in *@children
- -- L\trace "updating #{child}"
- L\push child\update, dt
-
- get: => @children[#@children]\get!
- getc: => @children[#@children]\getc!
-
- __tostring: => '<forwarder>'
-
class op_invoke extends Action
patch: (head) =>
return true if head == @head
@op\destroy! if @op
- @head = head
- assert @head.type == 'opdef', "cant op-invoke #{@head}"
- @op = @head\getc!!
-
+ def = head\const!\unwrap 'opdef', "cant op-invoke #{@head}"
+ @head, @op = head, def!
+
true
eval: (scope, tail) =>
- L\trace "evaling #{@}"
- args = L\push -> [L\push expr\eval, scope for expr in *tail]
+ children = L\push -> [L\push expr\eval, scope for expr in *tail]
+ value = @op\setup unpack [child.value for child in *children]
- -- Const 'op', with @op
- with @op
- \setup unpack args
+ ResultNode :children, :value, op: @op
class fn_invoke extends Action
-- @TODO:
@@ -48,27 +32,23 @@ class fn_invoke extends Action
true
eval: (outer_scope, tail) =>
- assert @head.type == 'fndef', "cant fn-invoke #{@head}"
- { :params, :body, :scope } = @head\getc!
+ { :params, :body, :scope } = @head\const!\unwrap 'fndef', "cant fn-invoke #{@head}"
assert #params == #tail, "argument count mismatch in #{@head}"
fn_scope = Scope @, scope
- for i=1,#params
- name = params[i]\getc!
- argm = tail[i]
- fn_scope\set name, L\push argm\eval, outer_scope
+ children = for i=1,#params
+ name = params[i]\unwrap 'sym'
+ with L\push tail[i]\eval, outer_scope
+ fn_scope\set name, .value
body = body\clone @tag
+ result = body\eval fn_scope
- body\eval fn_scope
-
-class do_expr extends Action
- eval: (scope, tail) =>
- UpdateChildren [(expr\eval scope) or Const.empty! for expr in *tail]
+ table.insert children, result
+ ResultNode :children, value: result.value
{
:op_invoke, :fn_invoke
- :UpdateChildren
}
diff --git a/core/parsing.moon b/core/parsing.moon
index 58dbf62..96d8125 100644
--- a/core/parsing.moon
+++ b/core/parsing.moon
@@ -1,4 +1,4 @@
-import Const from require 'core.const'
+import Const from require 'core.value'
import Cell, RootCell from require 'core.cell'
import Tag from require 'core.tag'
import R, S, P, V, C, Ct from require 'lpeg'
@@ -14,13 +14,14 @@ space = (wc^1 * (comment * wc^1)^0) / 1 -- required whitespace
mspace = (comment + wc)^0 / 1 -- optional whitespace
-- atoms
-sym = ((R 'az', 'AZ') + (S '-_+*/.!?')) ^ 1 / Const\parse 'sym'
+digit = R '09'
+first = (R 'az', 'AZ') + S '-_+*/.!?='
+sym = first * (first + digit)^0 / Const\parse 'sym'
strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Const\parse 'str', '\"'
strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Const\parse 'str', '\''
str = strd + strq
-digit = R '09'
int = digit^1
float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1)
num = (float + int) / Const\parse 'num'
@@ -43,7 +44,13 @@ cell = P {
:expr, :explist, :cell
}
-program = root * -1
+parse_error = (root, rest) ->
+ if #rest > 0
+ error "failed to parse, rest is:\n#{rest}"
+
+ root
+
+program = root * (C (P 1)^0) * -1 / parse_error
{
:comment
diff --git a/core/registry.moon b/core/registry.moon
index 3fc6d55..4dd5bbb 100644
--- a/core/registry.moon
+++ b/core/registry.moon
@@ -41,8 +41,9 @@ class Registry
-- (e.g. first [A] is solved, then [5.A] is solved)
continue if tag\index!
- L\trace "assigning new tag #{value} to #{tag} #{expr}"
- tag\set @next_tag!
+ next_tag = @next_tag!
+ L\trace "assigned new tag #{next_tag} to #{tag} #{expr}"
+ tag\set next_tag
@map[tag\index!] = expr
assert @ == @@active_registry, "not the active registry!"
diff --git a/core/scope.moon b/core/scope.moon
index ed4d67d..aaa38f2 100644
--- a/core/scope.moon
+++ b/core/scope.moon
@@ -1,10 +1,10 @@
-import Const from require 'core.const'
+import Value from require 'core.value'
class Scope
new: (@node, @parent) =>
@values = {}
- set_raw: (key, val) => @values[key] = Const.wrap val, key
+ set_raw: (key, val) => @values[key] = Value.wrap val, key
set: (key, val) =>
L\trace "setting #{key} = #{val} in #{@}"
@values[key] = val
@@ -22,7 +22,7 @@ class Scope
scope = @get start
assert scope and scope.type == 'scope', "cant find '#{prefix}#{start}' for '#{prefix}#{key}'"
- scope\getc!\get rest, "#{prefix}#{start}/"
+ scope\unwrap!\get rest, "#{prefix}#{start}/"
use: (other) =>
L\trace "using defs from #{other} in #{@}"
@@ -31,7 +31,7 @@ class Scope
from_table: (tbl) ->
with Scope!
- .values = { k, Const.wrap v, k for k,v in pairs tbl }
+ .values = { k, Value.wrap v, k for k,v in pairs tbl }
__tostring: =>
buf = "<Scope"
diff --git a/core/value.moon b/core/value.moon
new file mode 100644
index 0000000..f3f9796
--- /dev/null
+++ b/core/value.moon
@@ -0,0 +1,157 @@
+-- ALV Value types
+import Op, Action, FnDef from require 'core.base'
+
+local Scope
+load_ = ->
+ import Scope from require 'core.scope'
+
+ancestor = (klass) ->
+ assert klass, "cant find the ancestor of nil"
+ while klass.__parent
+ klass = klass.__parent
+ klass
+
+-- Result of evaluating an expression
+-- carries (all optional):
+-- - a Value
+-- - an Op (to update)
+-- - children (results of subexpressions that were evaluated)
+--
+-- ResultNodes form a tree that controls execution order and message passing
+-- between Ops.
+class ResultNode
+ -- params: table with optional keys op, value, children
+ new: (params={}) =>
+ @op = params.op
+ @value = params.value
+ @children = params.children or {}
+
+ -- unwrap value and assert that neither @op nor @children were set
+ value_only: (msg) =>
+ assert not @op and #@children == 0, msg or "pure expression expected"
+ @value
+
+ update: (dt) =>
+ for child in *@children
+ child\update dt
+ @op\update dt if @op
+
+ __tostring: =>
+ buf = "<result=#{@value}"
+ buf ..= " #{@op}" if @op
+ buf ..= " (#{#@children} children)" if #@children > 0
+ buf ..= ">"
+ buf
+
+local Const, Stream
+
+-- ALV Type wrapper
+class Value
+ -- @type - type name.
+ -- builtin types: * literals: sym, num, bool
+ -- * scope, opdef, fndef, builtin
+ -- @value - Lua value - access through :unwrap()
+ new: (@type, @value) =>
+
+ -- asserts value-constness
+ -- returns self (for chaining)
+ const: => error 'not a constant'
+
+ -- unwrap to the Lua type
+ -- asserts @type == type, msg if given
+ unwrap: (type, msg) =>
+ assert type == @type, msg or "#{@} is not a #{type}" if type
+ @value
+
+-- static
+ __tostring: =>
+ value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value
+ "<#{@@__name} #{@type}: #{value}>"
+ __eq: (other) => other.type == @type and other.value == @value
+ __inherited: (cls) =>
+ cls.__base.__tostring = @__tostring
+ cls.__base.__eq = @__eq
+
+ -- wrap a Lua type
+ @wrap: (val, name='(unknown)') ->
+ typ = switch type val
+ when 'number' then 'num'
+ when 'string' then 'str'
+ when 'table'
+ if base = rawget val, '__base'
+ -- a class
+ switch ancestor val
+ when Op then 'opdef'
+ when Action then 'builtin'
+ else
+ error "#{name}: cannot wrap class '#{val.__name}'"
+ elseif val.__class
+ -- an instance
+ switch ancestor val.__class
+ when Scope then 'scope'
+ when FnDef then 'fndef'
+ when Value
+ return val
+ else
+ error "#{name}: cannot wrap '#{val.__class.__name}' instance"
+ else
+ -- plain table
+ return Const 'scope', Scope.from_table val
+ else
+ error "#{name}: cannot wrap Lua type '#{type val}'"
+
+ Const typ, val
+
+class Stream extends Value
+ new: (@type, @value=nil) =>
+
+ set: (@value) =>
+ -- set dirty flag (?)
+
+class Const extends Value
+ new: (type, value, @raw) =>
+ super type, value
+
+-- Value interface
+ const: => @
+
+-- AST interface
+ eval: (scope) =>
+ value = switch @type
+ when 'num', 'str'
+ @
+ when 'sym'
+ assert (scope\get @value), "undefined reference to symbol '#{@value}'"
+ else
+ error "cannot evaluate #{@}"
+
+ ResultNode :value
+
+ quote: => @
+
+ stringify: => @raw
+
+ clone: (prefix) => @
+ -- in case of doubt:
+ -- clone: (prefix) => Const @type, @value, @raw
+
+-- static
+ unescape = (str) -> str\gsub '\\([\'"\\])', '%1'
+
+ @parse: (type, sep) =>
+ switch type
+ when 'num' then (match) -> @ 'num', (tonumber match), match
+ when 'sym' then (match) -> @ 'sym', match, match
+ when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep
+
+ @num: (num) -> Const 'num', num, tostring num
+ @str: (str) -> Const 'str', str, "'#{str}'"
+ @sym: (sym) -> Const 'sym', sym, sym
+ @bool: (bool) -> Const 'bool', bool, tostring bool
+
+{
+ :ResultNode
+ :Value
+ :Stream, :Const
+ :load_
+}
diff --git a/lib/debug.moon b/lib/debug.moon
index ece365f..f642c2e 100644
--- a/lib/debug.moon
+++ b/lib/debug.moon
@@ -3,12 +3,10 @@ import Op from require 'core'
class out extends Op
@doc: "(out name-str value) - log value to the console"
- setup: (name, @chld) =>
- @name = name\getc!
+ setup: (@name, @value) =>
update: (dt) =>
- @chld\update dt
- L\print "@name", @chld\get!
+ L\print "#{@name\unwrap 'str'}", @value\unwrap!
{
:out
diff --git a/lib/envelope.moon b/lib/envelope.moon
index 8e8fafe..eb79a64 100644
--- a/lib/envelope.moon
+++ b/lib/envelope.moon
@@ -1,4 +1,4 @@
-import Const, Op, Scope, eval from require 'core'
+import Stream, Op, Scope, eval from require 'core'
class ar_core extends Op
@doc: "(ar-core attack release gate)
@@ -8,21 +8,18 @@ goes from 0 to 1 in attack seconds, holds while gate is on, then goes back to 0
new: (...) =>
super ...
- @value = 0
+ @out = Stream 'num', 0
setup: (@a, @r, @gate) =>
assert @a, "ar requires an attack value"
assert @r, "ar requires a release value"
assert @gate, "ar requires a gate value"
+ @out
update: (dt) =>
- @a\update dt
- @r\update dt
- @gate\update dt
+ slope = if (@gate\unwrap 'bool') then (@a\unwrap! or 0.1) else -(@r\unwrap! or 0.5)
- slope = if @gate\get! then (@a\get! or 0.1) else -(@r\get! or 0.5)
-
- @value = math.min 1, math.max 0, @value + dt / slope
+ @out\set math.min 1, math.max 0, @out\unwrap! + dt / slope
scope = Scope.from_table { 'ar-core': ar_core }
scope\set 'ar', eval '(fn (a r) (fn (gate) (ar-core a r gate)))', scope
diff --git a/lib/gui.moon b/lib/gui.moon
index a2fee4b..474de5c 100644
--- a/lib/gui.moon
+++ b/lib/gui.moon
@@ -12,12 +12,11 @@ display value as a bar"
setup: (name, @chld) =>
@@instances[@name] = nil if @name
- @name = name\getc!
+ @name = name\const!\unwrap!
@@instances[@name] = @
update: (dt) =>
- @chld\update dt
- @value = @chld\get!
+ @value = @chld\unwrap!
destroy: =>
@@instances[@name] = nil
@@ -44,9 +43,11 @@ class key extends Op
setup: (@key) =>
assert @key
+ @out = Stream 'bool', false
+ @out
update: (dt) =>
- @value = lk.isDown @key\get!
+ @out\set lk.isDown @key\unwrap!
arguments, k = {}
for a in *arg
diff --git a/lib/logic.moon b/lib/logic.moon
index 1c274dd..c476033 100644
--- a/lib/logic.moon
+++ b/lib/logic.moon
@@ -1,37 +1,36 @@
-import Op from require 'core'
+import Stream, Op from require 'core'
unpack or= table.unpack
class BinOp extends Op
+ new: (...) =>
+ super ...
+ @out = Stream 'bool'
+
setup: (...) =>
@children = { ... }
assert #@children >= 2, "#{@} needs at least two parameters"
-
- update: (dt) =>
- for child in *@children
- child\update dt
+ @out
class eq extends BinOp
@doc: "(eq a b [c]...)
(== a b [c]...) - check for equality"
update: (dt) =>
- super\update dt
-
- @value = true
- val = @children[1]\get!
+ equal = true
+ val = @children[1]\unwrap!
for child in *@children[2,]
- @value and= val == child\get!
+ equal and= val == child\unwrap!
+ @out\set equal
class and_ extends BinOp
@doc: "(and a b [c]...) - AND values"
update: (dt) =>
- super\update dt
-
- @value = true
+ value = true
for child in *@children
- @value and= child\get!
+ value and= child\unwrap!
+ @out\set value
class or_ extends BinOp
@doc: "(or a b [c]...) - OR values
@@ -39,31 +38,30 @@ class or_ extends BinOp
subtracts all other arguments from a"
update: (dt) =>
- super\update dt
-
- @value = false
+ value = false
for child in *@children
- @value or= child\get!
+ value or= child\unwrap!
+ @out\set value
class not_ extends Op
@doc: "(not a) - boolean opposite"
setup: (@a) =>
+ @out = Stream 'bool'
+ @out
update: (dt) =>
- @a\update dt
-
- @value = not @a\get!
+ @out\set not @a\unwrap!
class bool extends Op
@doc: "(bool a) - convert to bool"
setup: (@a) =>
+ @out = Stream 'bool'
+ @out
update: (dt) =>
- @a\update dt
-
- @value = switch @a\get!
+ @out\set switch @a\unwrap!
when false, nil, 0
false
else
diff --git a/lib/math.moon b/lib/math.moon
index 54d3440..68dae3a 100644
--- a/lib/math.moon
+++ b/lib/math.moon
@@ -1,25 +1,25 @@
-import Op from require 'core'
+import Stream, Op, Const from require 'core'
unpack or= table.unpack
class BinOp extends Op
+ new: (...) =>
+ super ...
+ @out = Stream 'num', 0
+
setup: (...) =>
@children = { ... }
assert #@children >= 2, "#{@} needs at least two parameters"
-
- update: (dt) =>
- for child in *@children
- child\update dt
+ @out
class add extends BinOp
@doc: "(+ a b [c]...)
(add a b [c]...) - add values"
update: (dt) =>
- super\update dt
-
- @value = 0
+ value = 0
for child in *@children
- @value += child\get!
+ value += child\unwrap 'num'
+ @out\set value
class sub extends BinOp
@doc: "(- a b [c]...)
@@ -28,22 +28,20 @@ class sub extends BinOp
subtracts all other arguments from a"
update: (dt) =>
- super\update dt
-
- @value = @children[1]\get!
+ value = @children[1]\unwrap 'num'
for child in *@children[2,]
- @value -= child\get!
+ value -= child\unwrap 'num'
+ @out\set value
class mul extends BinOp
@doc: "(* a b [c]...)
(mul a b [c]...) - multiply values"
update: (dt) =>
- super\update dt
-
- @value = 1
+ value = 1
for child in *@children
- @value *= child\get!
+ value *= child\unwrap 'num'
+ @out\set value
class div extends BinOp
@doc: "(/ a b [c]...)
@@ -52,11 +50,26 @@ class div extends BinOp
divides a by all other arguments"
update: (dt) =>
- super\update dt
-
- @value = @children[1]\get!
+ value = @children[1]\unwrap 'num'
for child in *@children[2,]
- @value /= child\get!
+ value /= child\unwrap 'num'
+ @out\set value
+
+evenodd_op = (name, remainder) ->
+ class k extends Op
+ setup: (@a, @div=Const.num 2) =>
+ @out = Stream 'bool'
+ @out
+
+ update: (dt) =>
+ a, divi = (@a\unwrap 'num'), @div\unwrap 'num'
+ @out\set (a % divi) == remainder
+
+ k.__name = name
+ k.doc = "(#{name} a [div]) - check for #{name} divison
+
+div defaults to 2"
+ k
func_op = (name, arity, func) ->
k = class extends Op
@@ -65,12 +78,11 @@ func_op = (name, arity, func) ->
if arity != '*'
assert #@params == arity, "#{@} needs exactly #{arity} parameters"
- update: (dt) =>
- params = for param in *@params
- param\update dt
- param\get!
+ @out = Stream 'num'
+ @out
- @value = func unpack params
+ update: (dt) =>
+ @out\set func unpack [p\unwrap 'num' for p in *@params]
k.__name = name
k
@@ -85,6 +97,8 @@ module = {
:mod, '%': mod
mix: func_op 'mix', 3, (a, b, i) -> i*b + (1-i)*a
+ even: evenodd_op 'even', 0
+ odd: evenodd_op 'odd', 1
pi: math.pi, huge: math.huge
}
diff --git a/lib/midi/core.moon b/lib/midi/core.moon
index ea49a2c..891de0a 100644
--- a/lib/midi/core.moon
+++ b/lib/midi/core.moon
@@ -51,8 +51,10 @@ class Input
-- register a handler
-- mask is { :status, :chan, :a } (all keys optional)
+ -- returns mask for op convenience
attach: (mask, handler) =>
@listeners[mask] = handler
+ mask
detach: (mask) =>
@listeners[mask] = nil
@@ -83,8 +85,23 @@ class InOut
detach: (...) => @inp\detach ...
send: (...) => @out\send ...
+apply_range = (range, val) ->
+ if range.type == 'str'
+ switch range\unwrap!
+ when 'raw' then val
+ when 'uni' then val / 128
+ when 'bip' then val / 64 - 1
+ when 'rad' then val / 64 * math.pi
+ else
+ error "unknown range #{@range}"
+ elseif range.type == 'num'
+ val / 128 * range\unwrap!
+ else
+ error "range has to be a string or number"
+
{
:Input
:Output
:InOut
+ :apply_range
}
diff --git a/lib/midi/init.moon b/lib/midi/init.moon
index f522b6c..c9e2476 100644
--- a/lib/midi/init.moon
+++ b/lib/midi/init.moon
@@ -1,29 +1,29 @@
-import Const, Op from require 'core'
-import Input from require 'lib.midi.core'
+import Stream, Const, Op from require 'core'
+import Input, apply_range from require 'lib.midi.core'
-dispatch = Input 'system:midi_capture_6'
+dispatch = Input 'system:midi_capture_4'
class gate extends Op
@doc: "(midi/gate note [chan]) - gate from note-on and note-off messages"
- new: (...) =>
- super ...
-
destroy: =>
dispatch\detach @mask if @mask
setup: (note, chan) =>
dispatch\detach @mask if @mask
- note = note\getc 'num'
- chan = chan and chan\getc 'num'
+ note = note\const!\unwrap 'num'
+ chan = chan and chan\const!\unwrap 'num'
@value = false
@mask = dispatch\attach { :chan, a: note }, (status) ->
if status == 'note-on'
- @value = true
+ @out\set true
else if status == 'note-off'
- @value = false
+ @out\set false
+
+ @out = Stream 'bool', false
+ @out
update: (dt) => dispatch\tick!
@@ -43,26 +43,16 @@ range can be one of:
setup: (cc, chan, @range=Const.str'uni') =>
dispatch\detach @mask if @mask
- cc = cc\getc 'num'
- chan = chan and chan\getc 'num'
+ cc = cc\const!\unwrap 'num'
+ chan = chan and chan\const!\unwrap 'num'
- @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) -> @apply val
+ @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) ->
+ @out\set apply_range @range, val
- update: (dt) => dispatch\tick!
+ @out = Stream 'num', 0
+ @out
- apply: (val) =>
- @value = if @range.type == 'str'
- switch @range\get!
- when 'raw' then val
- when 'uni' then val / 128
- when 'bip' then val / 64 - 1
- when 'rad' then val / 64 * math.pi
- else
- error "unknown range #{@range}"
- elseif @range.type == 'num'
- val / 128 * @range\get!
- else
- error "range has to be a string or number"
+ update: (dt) => dispatch\tick!
{
:gate
diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon
index c7d7b06..778a724 100644
--- a/lib/midi/launchctl.moon
+++ b/lib/midi/launchctl.moon
@@ -1,66 +1,127 @@
-import Const, Op from require 'core'
-import InOut from require 'lib.midi.core'
+import Stream, Const, Op from require 'core'
+import InOut, apply_range from require 'lib.midi.core'
import bor, lshift from require 'bit32'
-launch = InOut 'system:midi_capture_6', 'system:midi_playback_6'
+launch = InOut 'system:midi_capture_4', 'system:midi_playback_4'
color = (r, g) -> bor 12, r, (lshift g, 4)
+class cc_seq extends Op
+ @doc: "(launctl/cc-seq i start chan [steps [range]]) - CC-Sequencer
+
+returns the value for the i-th step steps (buttons starting from start).
+steps defaults to 8.
+
+range can be one of:
+- 'raw' [ 0 - 128[
+- 'uni' [ 0 - 1[ (default)
+- 'bip' [-1 - 1[
+- 'rad' [ 0 - tau[
+- (num) [ 0 - num["
+
+ destroy: =>
+ launch\detach @mask if @mask
+
+ new: =>
+ @steps = {}
+ @out = Stream 'num'
+
+ setup: (@i, start, chan, steps=(Const.num 8), @range=Const.str 'uni') =>
+ launch\detach @mask if @mask
+
+ @start = start\const!\unwrap 'num'
+ @chan = chan\const!\unwrap 'num'
+ steps = steps\const!\unwrap 'num'
+
+ while steps > #@steps
+ table.insert @steps, 0
+ while steps < #@steps
+ table.remove @steps
+
+ @mask = launch\attach { status: 'control-change', chan: @chan }, (_, _, cc, val) -> @change cc, val
+ @out
+
+ change: (cc, val) =>
+ i = cc - @start
+ if i < #@steps
+ @steps[i+1] = val
+
+ update: (dt) =>
+ launch\tick!
+
+ curr_i = (@i\unwrap 'num') % #@steps
+
+ @out\set apply_range @range, @steps[curr_i+1]
+
+
class gate_seq extends Op
@doc: "(launctl/gate-seq i start chan [steps]) - Gate-Sequencer
-returns true or false for the i-th step steps (buttons starting from start)."
+returns true or false for the i-th step steps (buttons starting from start).
+steps defaults to 8."
destroy: =>
launch\detach @mask if @mask
new: =>
@steps = {}
- @value = false
+ @out = Stream 'bool'
setup: (@i, start, chan, steps=(Const.num 8)) =>
launch\detach @mask if @mask
- @start = start\getc 'num'
- @chan = chan\getc 'num'
- steps = steps\getc 'num'
+ for i=1, #@steps
+ launch\send 'note-on', @chan, (@start+i), 0, color 0, 0
+
+ @start = start\const!\unwrap 'num'
+ @chan = chan\const!\unwrap 'num'
+ steps = steps\const!\unwrap 'num'
while steps > #@steps
table.insert @steps, false
while steps < #@steps
table.remove @steps
- @mask = launch\attach { status: 'note-on', chan: @chan }, (_, _, note, _) -> @toggle note
+ for i=1, steps
+ @display i
+
+ @mask = launch\attach { status: 'note-on', chan: @chan }, (_, _, note, _) ->
+ @toggle note
+
+ @out
toggle: (note) =>
i = note - @start
val = @steps[i+1]
if val != nil
@steps[i+1] = not val
- curr_i = (@i\get 'num') % #@steps
- launch\send 'note-on', @chan, note, light @steps[i+1], i == curr_i
+ curr_i = (@i\unwrap 'num') % #@steps
+ @display i, i == curr_i
+
+ display: (i, active) =>
+ launch\send 'note-on', @chan, (@start + i), light @steps[i+1], active
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, 0
- when 'S ' then 0, 1
- when 'SA' then 0, 3
+ when ' A' then 1, 1
+ when 'S ' then 1, 0
+ when 'SA' then 3, 1
update: (dt) =>
launch\tick!
- @i\update dt
- curr_i = (@i\get 'num') % #@steps
+ curr_i = (@i\unwrap 'num') % #@steps
prev_i = (curr_i - 1) % #@steps
- launch\send 'note-on', @chan, (@start + curr_i), light @steps[curr_i+1], true
- launch\send 'note-on', @chan, (@start + prev_i), light @steps[prev_i+1], false
+ @display curr_i, true
+ @display prev_i, false
- @value = @steps[curr_i+1]
+ @out\set @steps[curr_i+1]
{
'gate-seq': gate_seq
+ 'cc-seq': cc_seq
}
diff --git a/lib/osc.moon b/lib/osc.moon
index dae6737..fed1697 100644
--- a/lib/osc.moon
+++ b/lib/osc.moon
@@ -1,4 +1,4 @@
-import Const, Op, FnDef from require 'core'
+import Stream, Op from require 'core'
import pack, unpack from require 'osc'
import dns, udp from require 'socket'
@@ -13,12 +13,9 @@ class out extends Op
setup: (@host, @port, @path, @value) =>
update: (dt) =>
- for p in *{@host, @port, @path, @value}
- L\push p\update, dt
-
- ip = dns.toip @host\get!
- port = @port\get!
- msg = pack @path\get!, @value\get!
+ ip = dns.toip @host\unwrap 'str'
+ port = @port\unwrap 'num'
+ msg = pack (@path\unwrap 'str'), @value\unwrap!
@@udp\sendto msg, ip, port
{
diff --git a/lib/pilot.moon b/lib/pilot.moon
index 1cbc070..2a7d21a 100644
--- a/lib/pilot.moon
+++ b/lib/pilot.moon
@@ -1,5 +1,5 @@
-import Const, Op, FnDef from require 'core'
-import dns, udp from require 'socket'
+import Op from require 'core'
+import udp from require 'socket'
conn = udp!
hex = "0123456789abcdef"
@@ -18,17 +18,12 @@ send = (tbl) ->
class play extends Op
@doc: "(play trig ch oct note [vel [len]]) - play a note when trig is live"
- setup: (@trig, @ch, @oct, @note, @vel, @len) =>
+ setup: (@trig, ...) =>
+ @vals = { ... }
update: (dt) =>
- vals = for c in *{@trig, @ch, @oct, @note, @vel, @len }
- c\update dt
- c\get!
-
- trig = table.remove vals, 1
-
- if trig
- send vals
+ if @trig\unwrap 'bool'
+ send [c\unwrap! for c in *@vals]
class effect extends Op
@doc: "(effect which a b) - set an effect
@@ -38,10 +33,7 @@ which is one of 'DIS', 'CHO', 'REV' or 'FEE'"
setup: (@which, @a, @b) =>
update: (dt) =>
- @which\update dt
- @a\update dt
- @b\update dt
- which, a, b = @which\get!, @a\get!, @b\get!
+ which, a, b = (@which\unwrap 'str'), @a\unwrap!, @b\unwrap!
if which and a and b
send { which, a, b }
{
diff --git a/lib/string.moon b/lib/string.moon
index 82e9702..6a9b7d7 100644
--- a/lib/string.moon
+++ b/lib/string.moon
@@ -1,4 +1,4 @@
-import Op from require 'core'
+import Stream, Op from require 'core'
class str extends Op
@doc: "(str v1 [v2]...)
@@ -6,12 +6,11 @@ class str extends Op
setup: (...) =>
@children = { ... }
+ @out = Stream 'str'
+ @out
update: (dt) =>
- for child in *@children
- child\update dt
-
- @value = table.concat [tostring child\get! for child in *@children]
+ @out\set table.concat [tostring child\unwrap! for child in *@children]
{
:str, '..': str
diff --git a/lib/time.moon b/lib/time.moon
index 89f947e..7ce8b84 100644
--- a/lib/time.moon
+++ b/lib/time.moon
@@ -1,4 +1,4 @@
-import Const, Op from require 'core'
+import Stream, Const, Op from require 'core'
class lfo extends Op
@doc: "(lfo freq [wave]) - low-frequency oscillator
@@ -12,19 +12,19 @@ wave selects the wave shape from the following (default sin):
tau = math.pi * 2
new: (...) =>
super ...
+ print "creating LFO"
+ @out = Stream 'num'
@phase = 0
default_wave = Const 'str', 'sin'
setup: (@freq, @wave=default_wave) =>
assert @freq, "lfo requires a frequency value"
L\trace "setup #{@}, freq=#{@freq}, wave=#{@wave}"
+ @out
update: (dt) =>
- @freq\update dt
- @wave\update dt
-
- @phase += dt * @freq\get!
- @value = switch @wave\get!
+ @phase += dt * @freq\unwrap 'num'
+ @out\set switch @wave\unwrap!
when 'sin' then .5 + .5 * math.cos @phase * tau
when 'saw' then @phase % 1
when 'tri' then math.abs (2*@phase % 2) - 1
@@ -37,45 +37,64 @@ ramps from 0 to max (default same as ramp) once every period seconds"
new: (...) =>
super ...
+ @out = Stream 'num'
@phase = 0
+ @out
setup: (@period, @max) =>
assert @period, "tick requires a period value"
update: (dt) =>
- @period\update dt
- max = if @max
- @max\update dt
- @max\get!
- else
- @period\get!
-
- @phase += dt / @period\get!
+ period = @period\unwrap 'num'
+ max = if @max then @max\unwrap! else period
+ @phase += dt / period
if @phase >= 1
@phase -= 1
- @value = @phase * max
+ @out\set @phase * max
class tick extends Op
@doc: "(tick period) - count ticks
-counts upwards every period seconds and returns the number of completed ticks."
+counts upwards by one every period seconds and returns the number of completed ticks."
new: (...) =>
super ...
+ @out = Stream 'num'
@phase = 0
setup: (@period) =>
assert @period, "tick requires a period value"
+ @out
update: (dt) =>
- @period\update dt
+ @phase += dt / @period\unwrap 'num'
+ @out\set math.floor @phase
+
+class every extends Op
+ @doc: "(every period) - sometimes true
- @phase += dt / @period\get!
- @value = math.floor @phase
+returns true once every period seconds."
+ new: (...) =>
+ super ...
+ @out = Stream 'bool'
+ @phase = 0
+
+ setup: (@period) =>
+ assert @period, "every requires a period value"
+ @out
+
+ update: (dt) =>
+ @phase += dt / @period\unwrap 'num'
+ if @phase > 1
+ @phase -= 1
+ @out\set true
+ else
+ @out\set false
{
:lfo
:ramp
:tick
+ :every
}
diff --git a/lib/util.moon b/lib/util.moon
index af5d8f8..192c489 100644
--- a/lib/util.moon
+++ b/lib/util.moon
@@ -1,4 +1,4 @@
-import Const, Op from require 'core'
+import Stream, Const, Op from require 'core'
class switch_ extends Op
@doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs
@@ -10,14 +10,16 @@ when i is a num, it is (floor)ed and the matching argument (starting from 0) is
setup: (@i, ...) =>
@choices = { ... }
- update: (dt) =>
- @i\update dt
- i = @i\get!
+ typ = @choices[1].type
+ for inp in *@choices[2,]
+ assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}"
- for choice in *@choices
- choice\update dt
+ @out = Stream typ
+ @out
- active = switch @i\get!
+ update: (dt) =>
+ i = @i\unwrap!
+ active = switch i
when true
@choices[1]
when false
@@ -25,7 +27,7 @@ when i is a num, it is (floor)ed and the matching argument (starting from 0) is
else
i = 1 + (math.floor i) % #@choices
@choices[i]
- @value = active and active\get!
+ @out\set active and active\unwrap!
class switch_pause extends Op
@doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs
@@ -35,10 +37,16 @@ like (switch ...) except that the unused inputs are paused."
setup: (@i, ...) =>
@choices = { ... }
+ typ = @choices[1].type
+ for inp in *@choices[2,]
+ assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}"
+
+ @out = Stream typ
+ @out
+
update: (dt) =>
- @i\update dt
- i = @i\get!
- active = switch @i\get!
+ i = @i\unwrap!
+ active = switch i
when true
@choices[1]
when false
@@ -47,21 +55,18 @@ like (switch ...) except that the unused inputs are paused."
i = 1 + (math.floor i) % #@choices
@choices[i]
- @value = if active
- active\update dt
- active\get!
+ @out\set if active
+ active\unwrap!
class edge extends Op
setup: (@i) =>
- @value = false
@last = false
+ @out = Stream @i.type
+ @out
update: (dt) =>
- @i\update dt
-
- now = @i\get!
-
- @value = not @last and now
+ now = @i\unwrap!
+ @out\set not @last and now
@last = now
class keep extends Op
@@ -71,12 +76,12 @@ always reproduces the last non-nil value the input produced or default.
default defaults to zero."
setup: (@i, @default=Const.num 0) =>
+ @out = Stream @i.type, @default.value
+ @out
update: (dt) =>
- @i\update dt
-
- next = @i\get!
- @value = next or @value or @default\get!
+ if next = @i\unwrap!
+ @out\set next
{
'switch': switch_