aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--copilot.moon10
-rw-r--r--core/base/action.moon33
-rw-r--r--core/builtin.moon487
-rw-r--r--core/cell.moon2
-rw-r--r--core/init.moon5
-rw-r--r--core/invoke.moon28
-rw-r--r--core/result.moon2
-rw-r--r--core/value.moon43
-rw-r--r--extra/docs.moon6
-rw-r--r--extra/layout.moon58
-rw-r--r--lib/logic.moon159
-rw-r--r--lib/math.moon218
-rw-r--r--lib/midi.moon169
-rw-r--r--lib/midi/core.moon70
-rw-r--r--lib/midi/launchctl.moon210
-rw-r--r--lib/osc.moon102
-rw-r--r--lib/pilot.moon90
-rw-r--r--lib/random.moon65
-rw-r--r--lib/sc.moon49
-rw-r--r--lib/string.moon17
-rw-r--r--lib/time.moon267
-rw-r--r--lib/util.moon188
22 files changed, 1306 insertions, 972 deletions
diff --git a/copilot.moon b/copilot.moon
index 600d2ed..8f98ab6 100644
--- a/copilot.moon
+++ b/copilot.moon
@@ -26,16 +26,18 @@ class Copilot
if @root
@registry\begin_tick!
- L\try "error evaluating:", ->
+ ok, error = Error.try "updating", ->
@root\tick_io!
@root\tick!
+ if not ok
+ print error
@registry\end_tick!
eval: =>
@registry\begin_eval!
- ast = L\try "error parsing:", parse, slurp @file
- if not ast
- L\error "error parsing"
+ ok, ast = Error.try "parsing '#{@file}'", parse, slurp @file
+ if not ok
+ print ast
@registry\rollback_eval!
return
diff --git a/core/base/action.moon b/core/base/action.moon
index cfca919..7ada4ba 100644
--- a/core/base/action.moon
+++ b/core/base/action.moon
@@ -15,9 +15,11 @@ class Action
--- create a new instance.
--
+ -- @tparam Cell cell the Cell to evaluate
-- @tparam Value head the (`AST:eval`d) `head` of the Cell to evaluate
- -- @tparam Tag tag the Tag of the expression to evaluate
- new: (@head, @tag) =>
+ new: (@cell, @head) =>
+ @tag = @cell.tag
+ @tag\replace @
--- perform the actual evaluation.
--
@@ -43,12 +45,13 @@ class Action
-- @tparam ?Action prev the previous Action instance
setup: (prev) =>
- --- the head of the `Cell` this Action was created for.
- --
+ --- the `Cell` this Action was created for.
+ -- @tfield Cell cell
+
+ --- the evaluated head of `cell`.
-- @tfield AST head
- --- the identity of the `Cell` this Action was created for.
- --
+ --- the identity of `cell`.
-- @tfield Tag tag
--- static functions
@@ -61,31 +64,29 @@ class Action
-- it pass it to `setup`. Register the `Action` with `tag`, evaluate it
-- and return the `Result`.
--
+ -- @tparam Cell cell the `Cell` being evaluated
-- @tparam Scope scope the active scope
- -- @tparam Tag tag the tag of the `Cell` being evaluated
-- @tparam Value head the (`AST:eval`d) head of the `Cell` being evaluated
- -- @tparam {AST,...} tail the raw AST parameters to the `Cell` being evaluated
-- @treturn Result the result of evaluation
- @eval_cell: (scope, tag, head, tail) =>
- last = tag\last!
+ @eval_cell: (cell, scope, head) =>
+ last = cell.tag\last!
compatible = last and (last.__class == @) and last.head == head
L\trace if compatible
- "reusing #{last} for #{tag} <#{@__name} #{head}>"
+ "reusing #{last} for #{cell.tag} <#{@__name} #{head}>"
else if last
- "replacing #{last} with new #{tag} <#{@__name} #{head}>"
+ "replacing #{last} with new #{cell.tag} <#{@__name} #{head}>"
else
- "initializing #{tag} <#{@__name} #{head}>"
+ "initializing #{cell.tag} <#{@__name} #{head}>"
- action = @ head, tag
+ action = @ cell, head
if compatible
action\setup last
else
last\destroy! if last
action\setup nil
- tag\replace action
- action\eval scope, tail
+ action\eval scope, cell\tail!
__tostring: => "<#{@@__name} #{@head}>"
__inherited: (cls) => cls.__base.__tostring = @__tostring
diff --git a/core/builtin.moon b/core/builtin.moon
index 49de80f..bd3183f 100644
--- a/core/builtin.moon
+++ b/core/builtin.moon
@@ -6,216 +6,282 @@
--
-- @module builtin
import Action, Op, FnDef, Input, match from require 'core.base'
-import Value from require 'core.value'
+import Value, LiteralValue from require 'core.value'
import Result from require 'core.result'
import Cell from require 'core.cell'
import Scope from require 'core.scope'
import Tag from require 'core.tag'
import op_invoke from require 'core.invoke'
-class doc extends Action
- @doc: "(doc sym) - print documentation in console
-
-prints the docstring for sym in the console"
-
- eval: (scope, tail) =>
- assert #tail == 1, "'doc' takes exactly one parameter"
-
- result = L\push tail[1]\eval, scope
- with Result children: { def }
- value = result\const!
- L\print "(doc #{tail[1]}):\n#{value.doc}\n"
-
-class def extends Action
- @doc: "(def sym1 val-expr1
- [sym2 val-expr2]...) - declare symbols in parent scope
-
-defines the symbols sym1, sym2, ... to resolve to the values of val-expr1, val-expr2, ...
-updates all val-exprs."
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail > 1, "'def' requires at least 2 arguments"
- assert #tail % 2 == 0, "'def' requires an even number of arguments"
-
- children = L\push ->
- return for i=1,#tail,2
- name, val_expr = tail[i], tail[i+1]
- name = (name\quote scope)\unwrap 'sym'
-
- with val_expr\eval scope
- scope\set name, \make_ref!
-
- Result :children
-
-class use extends Action
- @doc: "(use scope1 [scope2]...) - merge scopes into parent scope
-
-adds all symbols from scope1, scope2, ... to the parent scope.
-all scopes have to be eval-time constants."
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- for child in *tail
- result = L\push child\eval, scope
- value = result\const!
- scope\use value\unwrap 'scope', "'use' only works on scopes"
-
- Result!
-
-class require_ extends Action
- @doc: "(require name-str) - require a module
-
-returns the module's scope
-name-str has to be an eval-time constant."
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail == 1, "'require' takes exactly one parameter"
-
- result = L\push tail[1]\eval, scope
- name = result\const!
-
- L\trace @, "loading module #{name}"
- scope = Value.wrap require "lib.#{name\unwrap 'str'}"
- Result :value
-
-class import_ extends Action
- @doc: "(import sym1 [sym2]...) - require and define modules
-
-requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the current scope"
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail > 0, "'import' requires at least one arguments"
-
- for child in *tail
- name = (child\quote scope)\unwrap 'sym'
- value = Value.wrap require "lib.#{name}"
- scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope'
- Result!
-
-class import_star extends Action
- @doc: "(import* sym1 [sym2]...) - require and use modules
-
-requires modules sym1, sym2, ... and merges them into the current scope"
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail > 0, "'import' requires at least one arguments"
-
-
- for child in *tail
- name = (child\quote scope)\unwrap 'sym'
- value = Value.wrap require "lib.#{name}"
- scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope'
-
- Result!
-
-class fn extends Action
- @doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function
-
-the symbols p1, p2, ... will resolve to the arguments passed to the function."
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail == 2, "'fn' takes exactly two arguments"
- { params, body } = tail
-
- assert params.__class == Cell, "'fn's first 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"
- param\quote scope
-
- body = body\quote scope
- Result value: Value.wrap FnDef param_symbols, body, scope
-
-class defn extends Action
- @doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function
-
-declares a lambda (see (doc fn)) and defines it in the current scope"
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail == 3, "'defn' takes exactly three arguments"
- { name, params, body } = tail
-
- 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"
- param\quote scope
-
- body = body\quote scope
- fn = FnDef param_symbols, body, scope
-
- scope\set name, Result value: Value.wrap fn
- Result!
-
-class do_expr extends Action
- @doc: "(do expr1 [expr2]...) - update multiple expressions
-
-evaluates and continously updates expr1, expr2, ...
-the last expression's value is returned."
-
- eval: (scope, tail) =>
- scope = Scope scope
- Result 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
-
-bool has to be an eval-time constant. If it is truthy, this expression is equivalent
-to then-expr, otherwise it is equivalent to else-xpr if given, or nil otherwise."
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail >= 2, "'if' needs at least two parameters"
- assert #tail <= 3, "'if' needs at most three parameters"
-
- { xif, xthen, xelse } = tail
-
- xif = L\push xif\eval, scope
- xif = xif\const!\unwrap!
-
- if xif
- xthen\eval scope
- elseif xelse
- xelse\eval scope
-
-class trace_ extends Action
- @doc: "(trace! expr) - print an eval-time constant to the console"
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail == 1, "'trace!' takes exactly one parameter"
-
- with result = L\push tail[1]\eval, scope
- L\print "trace! #{tail[1]\stringify!}: #{result.value}"
-
-class trace extends Action
- @doc: "(trace expr) - print values to the console
-
-prints expr's value whenever it changes."
-
- class traceOp extends Op
- setup: (inputs) =>
- { prefix, value } = match 'str any', inputs
- super
- prefix: Input.cold prefix
- value: Input.value value
-
- tick: =>
- L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}"
-
- eval: (scope, tail) =>
- L\trace "evaling #{@}"
- assert #tail == 1, "'trace!' takes exactly one parameter"
-
- op = Value 'opdef', traceOp
- tag = @tag\clone Tag.parse '-1'
- prefix = Value.str tostring tail[1]
- op_invoke\eval_cell scope, tag, op, { prefix, tail[1] }
+doc = Value.meta
+ meta:
+ name: 'doc'
+ summary: "Print documentation in console."
+ examples: { '(doc sym)' }
+ description: "Print the documentation for `sym` to the console"
+
+ value: class extends Action
+ format_meta = =>
+ str = @summary
+ if @examples
+ for example in *@examples
+ str ..= '\n' .. example
+ if @description
+ str ..= '\n' .. @description\match '^\n*(.+)\n*$'
+ str
+
+ eval: (scope, tail) =>
+ assert #tail == 1, "'doc' takes exactly one parameter"
+
+ result = L\push tail[1]\eval, scope
+ with Result children: { def }
+ meta = result.value.meta
+ L\print "(doc #{tail[1]}):\n#{format_meta meta}\n"
+
+def = Value.meta
+ meta:
+ name: 'def'
+ summary: "Declare symbols in current scope."
+ examples: { '(def sym1 val-expr1 [sym2 val-expr2…])' }
+ description: "
+Define the symbols `sym1`, `sym2`, … to resolve to the values of `val-expr1`,
+`val-expr2`, …."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail > 1, "'def' requires at least 2 arguments"
+ assert #tail % 2 == 0, "'def' requires an even number of arguments"
+
+ children = L\push ->
+ return for i=1,#tail,2
+ name, val_expr = tail[i], tail[i+1]
+ name = (name\quote scope)\unwrap 'sym'
+
+ with val_expr\eval scope
+ scope\set name, \make_ref!
+
+ Result :children
+
+use = Value.meta
+ meta:
+ name: 'use'
+ summary: "Merge scopes into current scope."
+ examples: { '(use scope1 [scope2…])' }
+ description: "
+Copy all symbol definitions from `scope1`, `scope2`, … to the current scope.
+All arguments have to be evaltime constant."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ for child in *tail
+ result = L\push child\eval, scope
+ value = result\const!
+ scope\use value\unwrap 'scope', "'use' only works on scopes"
+
+ Result!
+
+require_ = Value.meta
+ meta:
+ name: 'require'
+ summary: "Load a module."
+ examples: { '(require name)' }
+ description: "Load a module and return its scope."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail == 1, "'require' takes exactly one parameter"
+
+ result = L\push tail[1]\eval, scope
+ name = result\const!
+
+ L\trace @, "loading module #{name}"
+ scope = Value.wrap require "lib.#{name\unwrap 'str'}"
+ Result :value
+
+import_ = Value.meta
+ meta:
+ name: 'import'
+ summary: "Require and define modules."
+ examples: { '(import sym1 [sym2…])' }
+ description: "
+Requires modules `sym1`, `sym2`, … and define them as `sym1`, `sym2`, … in the
+current scope."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail > 0, "'import' requires at least one arguments"
+
+ for child in *tail
+ name = (child\quote scope)\unwrap 'sym'
+ value = Value.wrap require "lib.#{name}"
+ scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope'
+ Result!
+
+import_star = Value.meta
+ meta:
+ name: 'import*'
+ summary: "Require and use modules."
+ examples: { '(import* sym1 [sym2…])' }
+ description: "
+Requires modules `sym1`, `sym2`, … and merges them into the current scope."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail > 0, "'import' requires at least one arguments"
+
+
+ for child in *tail
+ name = (child\quote scope)\unwrap 'sym'
+ value = Value.wrap require "lib.#{name}"
+ scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope'
+
+ Result!
+
+fn = Value.meta
+ meta:
+ name: 'fn'
+ summary: "Declare a function."
+ examples: { '(fn (p1 [p2…]) body-expr)' }
+ description: "
+The symbols `p1`, `p2`, ... will resolve to the arguments passed when the
+function is invoked."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail == 2, "'fn' takes exactly two arguments"
+ { params, body } = tail
+
+ assert params.__class == Cell, "'fn's first 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"
+ param\quote scope
+
+ body = body\quote scope
+ Result value: with Value.wrap FnDef param_symbols, body, scope
+ .meta = {
+ summary: "(user defined function)"
+ examples: { "(??? #{table.concat [p! for p in *param_symbols], ' '})" }
+ }
+
+defn = Value.meta
+ meta:
+ name: 'defn'
+ summary: "Define a function."
+ examples: { '(defn name-sym (p1 [p2…]) body-expr)' }
+ description: "
+Declare a function and define it as `name-sym` in the current scope.
+The symbols `p1`, `p2`, ... will resolve to the arguments passed when the
+function is invoked."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail == 3, "'defn' takes exactly three arguments"
+ { name, params, body } = tail
+
+ 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"
+ param\quote scope
+
+ body = body\quote scope
+
+ value = with Value.wrap FnDef param_symbols, body, scope
+ .meta =
+ :name
+ summary: "(user defined function)"
+ examples: { "(#{name} #{table.concat [p! for p in *param_symbols], ' '})" }
+
+ scope\set name, Result :value
+ Result!
+
+do_expr = Value.meta
+ meta:
+ name: 'do_expr'
+ summary: "Evaluate multiple expressions in a new scope."
+ examples: { '(do expr1 [expr2…])' }
+ description: "
+Evaluate `expr1`, `expr2`, … and return the value of the last expression."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ scope = Scope scope
+ Result children: [expr\eval scope for expr in *tail]
+
+if_ = Value.meta
+ meta:
+ name: 'if'
+ summary: "Make an evaltime const choice."
+ examples: { '(if bool then-expr [else-expr])' }
+ description: "
+`bool` has to be an evaltime constant. If it is truthy, this expression is equivalent
+to `then-expr`, otherwise it is equivalent to `else-xpr` if given, or nil otherwise."
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail >= 2, "'if' needs at least two parameters"
+ assert #tail <= 3, "'if' needs at most three parameters"
+
+ { xif, xthen, xelse } = tail
+
+ xif = L\push xif\eval, scope
+ xif = xif\const!\unwrap!
+
+ if xif
+ xthen\eval scope
+ elseif xelse
+ xelse\eval scope
+
+trace_ = Value.meta
+ meta:
+ name: 'trace!'
+ summary: "Trace an expression's value at evaltime."
+ examples: { '(trace! expr)' }
+
+ value: class extends Action
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail == 1, "'trace!' takes exactly one parameter"
+
+ with result = L\push tail[1]\eval, scope
+ L\print "trace! #{tail[1]\stringify!}: #{result.value}"
+
+trace = Value.meta
+ meta:
+ name: 'trace'
+ summary: "Trace an expression's values at runtime."
+ examples: { '(trace expr)' }
+
+ value: class extends Action
+ class traceOp extends Op
+ setup: (inputs) =>
+ { prefix, value } = match 'str any', inputs
+ super
+ prefix: Input.cold prefix
+ value: Input.value value
+
+ tick: =>
+ L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}"
+
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail == 1, "'trace!' takes exactly one parameter"
+
+ tag = @tag\clone Tag.parse '-1'
+ inner = Cell tag, {
+ LiteralValue 'opdef', traceOp, 'trace'
+ Value.str tostring tail[1]
+ tail[1]
+ }
+ inner\eval scope
{
:doc
@@ -226,8 +292,17 @@ prints expr's value whenever it changes."
import: import_
'import*': import_star
- true: Value.bool true
- false: Value.bool false
+ true: Value.meta
+ meta:
+ name: 'true'
+ summary: "The boolean constant `true`."
+ value: Value.bool true
+
+ false: Value.meta
+ meta:
+ name: 'false'
+ summary: "The boolean constant `false`."
+ value: Value.bool false
:fn, :defn
'do': do_expr
diff --git a/core/cell.moon b/core/cell.moon
index 2109ade..b7f4a1d 100644
--- a/core/cell.moon
+++ b/core/cell.moon
@@ -83,7 +83,7 @@ class Cell
else
error "cannot evaluate expr with head #{head}"
- Action\eval_cell scope, @tag, head, @tail!
+ Action\eval_cell @, scope, head
--- quote this Cell, preserving its identity.
--
diff --git a/core/init.moon b/core/init.moon
index 00e3cd4..1aa0d2d 100644
--- a/core/init.moon
+++ b/core/init.moon
@@ -49,7 +49,10 @@ globals = Scope.from_table require 'core.builtin'
:Registry, :SimpleRegistry, :Tag
:globals
- parse: program\match
+
+ parse: (str) ->
+ assert (program\match str), Error 'syntax', "failed to parse"
+
eval: (str, inject) ->
scope = Scope nil, globals
scope\use inject if inject
diff --git a/core/invoke.moon b/core/invoke.moon
index 97d3949..654e436 100644
--- a/core/invoke.moon
+++ b/core/invoke.moon
@@ -8,6 +8,20 @@ import Action from require 'core.base'
import Scope from require 'core.scope'
import Error from require 'core.error'
+get_name = (value, raw) ->
+ meta = if value.meta then value.meta.name
+ locl = if raw and raw.type == 'sym' then raw!
+
+ if locl
+ if meta and meta != locl
+ "'#{meta}' (local '#{locl}')"
+ else
+ "'#{locl}'"
+ else if meta
+ "'#{meta}'"
+ else
+ "(unnamed)"
+
--- `Action` implementation that invokes an `Op`.
--
-- @type op_invoke
@@ -41,7 +55,9 @@ class op_invoke extends Action
-- @treturn Result
eval: (scope, tail) =>
children = [L\push expr\eval, scope for expr in *tail]
- Error.wrap "invoking #{@op}#{@tag}", @op\setup, [result for result in *children], scope
+
+ frame = "invoking op #{get_name @head, @cell\head!} at [#{@tag}]"
+ Error.wrap frame, @op\setup, [result for result in *children], scope
any_dirty = false
for input in @op\all_inputs!
@@ -81,9 +97,13 @@ class fn_invoke extends Action
-- @tparam {AST,...} tail the arguments to this expression
-- @treturn Result the result of this evaluation
eval: (outer_scope, tail) =>
- { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}"
+ name = get_name @head, @cell\head!
+ frame = "invoking function #{name} at [#{@tag}]"
- assert #params == #tail, "argument count mismatch in #{@head}"
+ { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}"
+ if #params != #tail
+ error with Error 'argument', "expected #{#params} arguments, found #{#tail}"
+ \add_frame frame
fn_scope = Scope scope, outer_scope
@@ -93,7 +113,7 @@ class fn_invoke extends Action
fn_scope\set name, \make_ref!
clone = body\clone @tag
- result = Error.wrap "invoking function #{body.tag} at #{@tag}", clone\eval, fn_scope
+ result = Error.wrap frame, clone\eval, fn_scope
table.insert children, result
Result :children, value: result.value
diff --git a/core/result.moon b/core/result.moon
index 357a868..52203da 100644
--- a/core/result.moon
+++ b/core/result.moon
@@ -61,9 +61,7 @@ class Result
for stream in @op\all_inputs!
if stream\dirty!
self_dirty = true
- break
- -- L\trace "#{@op} is #{if self_dirty then 'dirty' else 'clean'}"
return unless self_dirty
@op\tick!
diff --git a/core/value.moon b/core/value.moon
index 7f5a93d..da7f6d0 100644
--- a/core/value.moon
+++ b/core/value.moon
@@ -72,23 +72,22 @@ class Value
-- - `fndef` - `value` is a `FnDef` instance
-- - `scope` - `value` is a `Scope` instance
--
- -- @tfield string type the type name
+ -- @tfield string type
--- the wrapped Lua value.
+ -- @tfield any value
+
+ --- documentation metadata.
--
- -- the following builtin typenames are used:
+ -- an optional table containing metadata for error messages and
+ -- documentation. The following keys are recognized:
--
- -- - `str` - strings, `value` is a Lua string
- -- - `sym` - symbols, `value` is a Lua string
- -- - `num` - numbers, `value` is a Lua number
- -- - `bool` - booleans, `value` is a Lua boolean
- -- - `bang` - trigger signals, `value` is a Lua boolean
- -- - `opdef` - `value` is an `Op` subclass
- -- - `builtin` - `value` is an `Action` subclass
- -- - `fndef` - `value` is a `FnDef` instance
- -- - `scope` - `value` is a `Scope` instance
+ -- - `name`: optional name
+ -- - `summary`: single-line description (markdown)
+ -- - `examples`: optional list of single-line code examples
+ -- - `description`: optional full-text description (markdown)
--
- -- @tfield any value the wrapped value
+ -- @tfield ?table meta
--- AST interface
--
@@ -139,6 +138,7 @@ class Value
-- @tparam any value the Lua value to be accessed through `unwrap`
-- @tparam string raw the raw string that resulted in this value. Used by `parsing`.
new: (@type, @value, @raw) =>
+ @meta = {}
unescape = (str) -> str\gsub '\\([\'"\\])', '%1'
--- create a capture-function (for parsing with Lpeg).
@@ -157,6 +157,7 @@ class Value
--
-- @tparam any val the value to wrap
-- @tparam[opt] string name the name of this value (for error logging)
+ -- @treturn Value
@wrap: (val, name='(unknown)') ->
typ = switch type val
when 'number' then 'num'
@@ -188,21 +189,39 @@ class Value
--- create a constant number.
-- @tparam number num the number
+ -- @treturn Value
@num: (num) -> Value 'num', num, tostring num
--- create a constant string.
-- @tparam string str the string
+ -- @treturn Value
@str: (str) -> Value 'str', str, "'#{str}'"
--- create a constant symbol.
-- @tparam string sym the symbol
+ -- @treturn Value
@sym: (sym) -> Value 'sym', sym, sym
--- create a constant boolean.
-- @tparam boolean bool the boolean
+ -- @treturn Value
@bool: (bool) -> Value 'bool', bool, tostring bool
+ --- wrap and document a value.
+ --
+ -- wraps `args.value` using `wrap`, then assigns `meta`.
+ --
+ -- @tparam table args table with keys `value` and `meta`
+ -- @treturn Value
+ @meta: (args) ->
+ with Value.wrap args.value
+ .meta = args.meta if args.meta
+
+class LiteralValue extends Value
+ eval: => Result value: @
+
{
:Value
+ :LiteralValue
:load_
}
diff --git a/extra/docs.moon b/extra/docs.moon
index d1553df..f43ecfa 100644
--- a/extra/docs.moon
+++ b/extra/docs.moon
@@ -1,6 +1,6 @@
import Value, Scope from require 'core'
import render, layout, autoref from require 'extra.layout'
-import section, h1, h2, p, ul, li, a, code, r from require 'extra.dom'
+import section, h1, h2, h3, p, ul, li, a, code, r from require 'extra.dom'
export OUT, BASE, require
{ OUT, command } = arg
@@ -33,6 +33,10 @@ spit OUT, switch command
title: "#{name} reference"
body: section {
h2 (code name), ' module reference'
+ h3 'index'
+ ul for key, res in opairs module.values
+ li render key, res.value, nil, true
+ h3 'details'
ul for key, res in opairs module.values
li render key, res.value
}
diff --git a/extra/layout.moon b/extra/layout.moon
index a3d67c8..dd309dc 100644
--- a/extra/layout.moon
+++ b/extra/layout.moon
@@ -1,26 +1,48 @@
-v = require 'core.version'
+version = require 'core.version'
+import compile from require 'discount'
+
+render_meta = (meta) ->
+ import p, code from require 'extra.dom'
+ contents = {}
+ if meta.examples
+ -- table.insert contents, h4 'signature'
+ examples = p table.concat [code e for e in *meta.examples], ' '
+ table.insert contents, examples
+ if meta.description
+ description = compile meta.description\match '^\n*(.+)\n*$'
+ table.insert contents, description.body
+
+ contents
-- render an ALV Value to a HTML string
-render = (name, value, prefix=nil) ->
+render = (name, value, prefix=nil, index=false) ->
import div, label, code, ul, li, i, a, pre from require 'extra.dom'
id = if prefix then "#{prefix}/#{name}" else name
type = i value.type
+ assert value.meta, "#{id} doesn't have any metadata!"
+ summary = assert value.meta.summary, "#{id} doesn't have a summary!"
- content = switch value.type
- when 'scope'
- ul for k, result in opairs value!.values
- li render k, result.value, id
- when 'opdef', 'builtin'
- pre value!.doc
- when 'num', 'str', 'bool'
- code tostring value!
-
- div {
- :id, class: 'def'
- label (a (code name), href: "##{id}"), ' (', type, '):'
- div content, class: 'nest'
- }
+ if index
+ div {
+ label (a (code name), href: "##{id}"), ' (', type, '): &ensp;&ndash;&ensp;'
+ summary
+ }
+ else
+ content = switch value.type
+ when 'scope'
+ ul for k, result in opairs value!.values
+ li render k, result.value, id
+ else
+ render_meta value.meta
+
+ content.class = 'nest'
+ div {
+ :id, class: 'def'
+ label (a (code name), href: "##{id}"), ' (', type, '): &ensp;&ndash;&ensp;'
+ summary
+ div content
+ }
-- generate a relative link
abs = (page) ->
@@ -78,7 +100,7 @@ layout = (opts) ->
span {
a 'alive', href: abs 'index.html'
' '
- code v.tag
+ code version.tag
' documentation'
}
div class: 'grow'
@@ -94,7 +116,7 @@ layout = (opts) ->
"alive documentation"
foot = footer div {
'alive '
- a (code v.tag), href: v.web
+ a (code version.tag), href: version.web
', generated '
os.date '!%Y-%m-%d %T'
}
diff --git a/lib/logic.moon b/lib/logic.moon
index 7264be0..157d190 100644
--- a/lib/logic.moon
+++ b/lib/logic.moon
@@ -1,4 +1,4 @@
-import Op, Input, Error, match from require 'core.base'
+import Op, Value, Input, Error, match from require 'core.base'
all_same = (first, list) ->
for v in *list
@@ -31,77 +31,98 @@ class ReduceOp extends Op
@out\set accum
-class eq extends Op
- @doc: "(eq a b [c]...)
-(== a b [c]...) - check for equality
+eq = Value.meta
+ meta:
+ name: 'eq'
+ summary: "Check for equality."
+ examples: { '(== a b [c]...)', '(eq a b [c]...)' }
+ description: "`true` if the types and values of all arguments are equal."
+
+ value: class extends Op
+ new: => super 'bool', false
+
+ setup: (inputs) =>
+ { first, rest } = match "any *any", inputs
+ same = all_same first\type!, [i\type! for i in *rest]
+
+ super if same
+ {
+ first: Input.value first
+ rest: [Input.value v for v in *rest]
+ }
+ else
+ {}
+
+ tick: =>
+ if not @inputs.first
+ @out\set false
+ return
+
+ { :first, :rest } = @unwrap_all!
+
+ equal = true
+ for other in *rest
+ if first != other
+ equal = false
+ break
-If the value types dont match, the result is an eval-time constant 'false'."
+ @out\set equal
- new: => super 'bool', false
- setup: (inputs) =>
- { first, rest } = match "any *any", inputs
- same = all_same first\type!, [i\type! for i in *rest]
+not_eq = Value.meta
+ meta:
+ name: 'not-eq'
+ summary: "Check for inequality."
+ examples: { '(!= a b [c]...)', '(not-eq a b [c]...)' }
+ description: "`true` if types or values of any two arguments are different."
- super if same
- {
- first: Input.value first
- rest: [Input.value v for v in *rest]
- }
- else
- {}
+ value: class extends Op
+ new: => super 'bool'
- tick: =>
- if not @inputs.first
- @out\set false
- return
+ setup: (inputs) =>
+ assert #inputs > 1, Error 'argument', "need at least two values"
+ super [Input.value v for v in *inputs]
- { :first, :rest } = @unwrap_all!
+ tick: =>
+ if not @inputs[1]
+ @out\set true
+ return
- equal = true
- for other in *rest
- if first != other
- equal = false
- break
+ diff = true
+ for a=1, #@inputs-1
+ for b=a+1, #@inputs
+ if @inputs[a].stream == @inputs[b].stream
+ diff = false
+ break
- @out\set equal
+ break unless diff
-class not_eq extends Op
- @doc: "(not-eq a b [c]...)
-(!= a b [c]...) - check for inequality"
- new: => super 'bool'
+ @out\set diff
- setup: (inputs) =>
- assert #inputs > 1, Error 'argument', "need at least two values"
- super [Input.value v for v in *inputs]
+and_ = Value.meta
+ meta:
+ name: 'and'
+ summary: "Logical AND."
+ examples: { '(and a b [c…])' }
+ value: class extends ReduceOp
+ fn: (a, b) -> a and b
- tick: =>
- if not @inputs[1]
- @out\set true
- return
-
- diff = true
- for a=1, #@inputs-1
- for b=a+1, #@inputs
- if @inputs[a].stream == @inputs[b].stream
- diff = false
- break
+or_ = Value.meta
+ meta:
+ name: 'or'
+ summary: "Logical OR."
+ examples: { '(or a b [c…])' }
+ value: class extends ReduceOp
+ fn: (a, b) -> a or b
- break unless diff
+not_ = Value.meta
+ meta:
+ name: 'not'
+ summary: "Logical NOT."
+ examples: { '(not a)' }
- @out\set diff
-
-class and_ extends ReduceOp
- @doc: "(and a b [c]...) - AND values"
- fn: (a, b) -> a and b
-
-class or_ extends ReduceOp
- @doc: "(or a b [c]...) - OR values"
- fn: (a, b) -> a or b
-
-class not_ extends Op
- @doc: "(not a) - boolean opposite"
- new: => super 'bool'
+ value: class extends Op
+ new: => super 'bool'
setup: (inputs) =>
{ value } = match 'any', inputs
@@ -109,15 +130,21 @@ class not_ extends Op
tick: => @out\set not tobool @inputs.value!
-class bool extends Op
- @doc: "(bool a) - convert to bool"
- new: => super 'bool'
+bool = Value.meta
+ meta:
+ name: 'bool'
+ summary: "Cast value to bool."
+ examples: { '(bool a)' }
+ description: "`false` if a is `false`, `nil` or `0`, `true` otherwise."
- setup: (inputs) =>
- { value } = match 'any', inputs
- super value: Input.value value
+ value: class extends Op
+ new: => super 'bool'
+
+ setup: (inputs) =>
+ { value } = match 'any', inputs
+ super value: Input.value value
- tick: => @out\set tobool @inputs\value!
+ tick: => @out\set tobool @inputs\value!
{
:eq, '==': eq
diff --git a/lib/math.moon b/lib/math.moon
index 84e6682..69d83bb 100644
--- a/lib/math.moon
+++ b/lib/math.moon
@@ -17,36 +17,31 @@ class ReduceOp extends Op
accum = @.fn accum, val
@out\set accum
-class add extends ReduceOp
- @doc: "(+ a b [c]...)
-(add a b [c]...) - add values"
-
- fn: (a, b) -> a + b
-
-class sub extends ReduceOp
- @doc: "(- a b [c]...)
-(sub a b [c]...) - subtract values
-
-subtracts all other arguments from a"
-
- fn: (a, b) -> a - b
-
-class mul extends ReduceOp
- @doc: "(* a b [c]...)
-(mul a b [c]...) - multiply values"
+func_op = (arity, func) ->
+ class extends Op
+ new: => super 'num'
- fn: (a, b) -> a * b
+ setup: (inputs) =>
+ { params } = match '*num', inputs
+ if arity != '*'
+ err = Error 'argument', "need exactly #{arity} arguments"
+ assert #params == arity, err
+ super [Input.value p for p in *params]
-class div extends ReduceOp
- @doc: "(/ a b [c]...)
-(div a b [c]...) - divide values
+ tick: => @out\set func unpack @unwrap_all!
-divides a by all other arguments"
+func_def = (name, args, func, summary) ->
+ _, arity = args\gsub ' ', ' '
- fn: (a, b) -> a / b
+ Value.meta
+ meta:
+ :name
+ :summary
+ examples: { "(#{name} #{args})" }
+ value: func_op arity+1, func
-evenodd_op = (name, remainder) ->
- class k extends Op
+evenodd_op = (remainder) ->
+ class extends Op
new: => super 'bool'
setup: (inputs) =>
@@ -59,57 +54,144 @@ evenodd_op = (name, remainder) ->
{ :val, :div } = @unwrap_all!
@out\set (val % div) == remainder
- k.__name = name
- k.doc = "(#{name} a [div]) - check for #{name} divison
-
-div defaults to 2"
- k
-
-func_op = (name, arity, func) ->
- k = class extends Op
- new: => super 'num'
-
- setup: (inputs) =>
- { params } = match '*num', inputs
- if arity != '*'
- err = Error 'argument', "need exactly #{arity} arguments"
- assert #params == arity, err
- super [Input.value p for p in *params]
-
- tick: => @out\set func unpack @unwrap_all!
-
- k.__name = name
- k
-
-mod = func_op 'mod', 2, (a, b) -> a % b
-
-module = {
+add = Value.meta
+ meta:
+ name: 'add'
+ summary: "Add values."
+ examples: { '(+ a b [c…])', '(add a b [c…])' }
+ description: "Sum all arguments."
+ value: class extends ReduceOp
+ fn: (a, b) -> a + b
+
+sub = Value.meta
+ meta:
+ name: 'sub'
+ summary: "Subtract values."
+ examples: { '(- a b [c…])', '(sub a b [c…])' }
+ description: "Subtract all other arguments from `a`."
+ value: class extends ReduceOp
+ fn: (a, b) -> a - b
+
+mul = Value.meta
+ meta:
+ name: 'mul'
+ summary: "Multiply values."
+ examples: { '(* a b [c…])', '(mul a b [c…])' }
+ value: class extends ReduceOp
+ fn: (a, b) -> a * b
+
+div = Value.meta
+ meta:
+ name: 'div'
+ summary: "Divide values."
+ examples: { '(/ a b [c…])', '(div a b [c…])' }
+ description: "Divide `a` by all other arguments."
+ value: class extends ReduceOp
+ fn: (a, b) -> a / b
+
+pow = Value.meta
+ meta:
+ name: 'pow'
+ summary: "Raise to a power."
+ examples: { '(^ base exp)', '(pow base exp' }
+ description: "Raise `base` to the power `exp`."
+ value: class extends ReduceOp
+ fn: (a, b) -> a ^ b
+
+mod = Value.meta
+ meta:
+ name: 'mod'
+ summary: 'Modulo operator.'
+ examples: { '(% num div)', '(mod num div)' }
+ description: "Calculate remainder of division by `div`."
+ value: func_op 2, (a, b) -> a % b
+
+even = Value.meta
+ meta:
+ name: 'even'
+ summary: 'Check whether val is even.'
+ examples: { '(even val [div])' }
+ description: "`true` if dividing `val` by `div` has remainder zero.
+`div` defaults to 2."
+ value: evenodd_op 0
+
+odd = Value.meta
+ meta:
+ name: 'odd'
+ summary: 'Check whether val is odd.'
+ examples: { '(odd val [div])' }
+ description: "`true` if dividing `val` by `div` has remainder one.
+`div` defaults to 2."
+ value: evenodd_op 1
+
+mix = Value.meta
+ meta:
+ name: 'mix'
+ summary: 'Linearly interpolate.'
+ examples: { '(mix a b i)' }
+ description: "Interpolate between `a` and `b` using `i` in range 0-1."
+ value: func_op 3, (a, b, i) -> i*b + (1-i)*a
+
+min = Value.meta
+ meta:
+ name: 'min'
+ summary: "Find the minimum."
+ examples: { '(min a b [c…])' }
+ description: "Return the lowest of arguments."
+ value: func_op '*', math.min
+
+max = Value.meta
+ meta:
+ name: 'max'
+ summary: "Find the maximum."
+ examples: { '(max a b [c…])' }
+ description: "Return the highest of arguments."
+ value: func_op '*', math.min
+
+cos = func_def 'cos', 'alpha', math.cos, "Cosine function (radians)."
+sin = func_def 'sin', 'alpha', math.sin, "Sine function (radians)."
+tan = func_def 'tan', 'alpha', math.tan, "Tangent function (radians)."
+acos = func_def 'acos', 'cos', math.acos, "Inverse cosine function (radians)."
+asin = func_def 'asin', 'sin', math.asin, "Inverse sine function (radians)."
+atan = func_def 'atan', 'tan', math.atan, "Inverse tangent function (radians)."
+atan2 = func_def 'atan2', 'y x', math.atan2, "Inverse tangent function (two argument version)."
+cosh = func_def 'cosh', 'alpha', math.cosh, "Hyperbolic cosine function (radians)."
+sinh = func_def 'sinh', 'alpha', math.sinh, "Hyperbolic sine function (radians)."
+tanh = func_def 'tanh', 'alpha', math.tanh, "Hyperbolic tangent function (radians)."
+
+floor = func_def 'floor', 'val', math.floor, "Round towards negative infinity."
+ceil = func_def 'ceil', 'val', math.ceil, "Round towards positive infinity."
+abs = func_def 'abs', 'val', math.abs, "Get the absolute value."
+
+exp = func_def 'exp', 'exp', math.floor, "*e* number raised to a power."
+log = func_def 'log', 'val base', math.log, "Logarithm with given base."
+log10 = func_def 'log10', 'val', math.log10, "Logarithm with base 10."
+sqrt = func_def 'sqrt', 'val', math.sqrt, "Square root function."
+
+{
:add, '+': add
:sub, '-': sub
:mul, '*': mul
:div, '/': div
+ :pow, '^': pow
:mod, '%': mod
- mix: func_op 'mix', 3, (a, b, i) -> i*b + (1-i)*a
- even: evenodd_op 'even', 0
- odd: evenodd_op 'odd', 1
+ :even, :odd
- pi: math.pi
- tau: math.pi*2
- huge: math.huge
-}
-
-for name, arity in pairs {
- exp: 1, log: 2, log10: 1, sqrt: 1
+ :mix
+ :min, :max
- cos: 1, sin: 1, tan: 1
- asin: 1, acos: 1, atan: 1, atan2: 2
- sinh: 1, cosh: 1, tanh: 1
+ pi: with Value.wrap math.pi
+ .meta = summary: 'The pi constant.'
+ tau: with Value.wrap math.pi*2
+ .meta = summary: 'The tau constant.'
+ huge: with Value.wrap math.huge
+ .meta = summary: 'Positive infinity constant.'
- min: '*', max: '*'
+ :sin, :cos, :tan
+ :asin, :acos, :atan, :atan2
+ :sinh, :cosh, :tanh
- floor: 1, ceil: 1, abs: 1
+ :floor, :ceil, :abs
+ :exp, :log, :log10, :sqrt
}
- module[name] = func_op name, arity, math[name]
-
-module
diff --git a/lib/midi.moon b/lib/midi.moon
index 33fe936..e978702 100644
--- a/lib/midi.moon
+++ b/lib/midi.moon
@@ -1,63 +1,73 @@
import Value, Op, Input, match from require 'core.base'
import input, output, inout, apply_range from require 'lib.midi.core'
-class gate extends Op
- @doc: "(midi/gate port note [chan]) - gate from note-on and note-off messages"
-
- new: =>
- super 'bool', false
-
- setup: (inputs) =>
- { port, note, chan } = match 'midi/port num num?', inputs
- super
- port: Input.io port
- note: Input.value note
- chan: Input.value chan or Value.num -1
-
- tick: =>
- { :port, :note, :chan } = @inputs
-
- if note\dirty! or chan\dirty!
- @out\set false
-
- if port\dirty!
- for msg in port!\receive!
- if msg.a == note! and (chan! == -1 or msg.chan == chan!)
- if msg.status == 'note-on'
- @out\set true
- elseif msg.status == 'note-off'
- @out\set false
-
-class trig extends Op
- @doc: "(midi/trig port note [chan]) - gate from note-on and note-off messages"
-
- new: =>
- super 'bang', false
-
- setup: (inputs) =>
- { port, note, chan } = match 'midi/port num num?', inputs
- super
- port: Input.io port
- note: Input.value note
- chan: Input.value chan or Value.num -1
-
- tick: =>
- { :port, :note, :chan } = @inputs
-
- if note\dirty! or chan\dirty!
- @out\set false
-
- if port\dirty!
- for msg in port!\receive!
- if msg.a == note! and (chan! == -1 or msg.chan == chan!)
- if msg.status == 'note-on'
- @out\set true
-
-
-class cc extends Op
- @doc: "(midi/cc port cc [chan [range]]) - MIDI CC to number
-
-range can be one of:
+gate = Value.meta
+ meta:
+ name: 'gate'
+ summary: "gate from note-on and note-off messages."
+ examples: { '(midi/gate port note [chan])' }
+
+ value: class extends Op
+ new: =>
+ super 'bool', false
+
+ setup: (inputs) =>
+ { port, note, chan } = match 'midi/port num num?', inputs
+ super
+ port: Input.io port
+ note: Input.value note
+ chan: Input.value chan or Value.num -1
+
+ tick: =>
+ { :port, :note, :chan } = @inputs
+
+ if note\dirty! or chan\dirty!
+ @out\set false
+
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.a == note! and (chan! == -1 or msg.chan == chan!)
+ if msg.status == 'note-on'
+ @out\set true
+ elseif msg.status == 'note-off'
+ @out\set false
+
+trig = Value.meta
+ meta:
+ name: 'trig'
+ summary: "`bang`s from note-on messages."
+ examples: { '(midi/trig port note [chan])' }
+
+ value: class extends Op
+ new: =>
+ super 'bang', false
+
+ setup: (inputs) =>
+ { port, note, chan } = match 'midi/port num num?', inputs
+ super
+ port: Input.io port
+ note: Input.value note
+ chan: Input.value chan or Value.num -1
+
+ tick: =>
+ { :port, :note, :chan } = @inputs
+
+ if note\dirty! or chan\dirty!
+ @out\set false
+
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.a == note! and (chan! == -1 or msg.chan == chan!)
+ if msg.status == 'note-on'
+ @out\set true
+
+trig = Value.meta
+ meta:
+ name: 'trig'
+ summary: "`num` from cc-change messages."
+ examples: { '(midi/cc port cc [chan [range]])' }
+ description: "
+`range` can be one of:
- 'raw' [ 0 - 128[
- 'uni' [ 0 - 1[ (default)
- 'bip' [-1 - 1[
@@ -65,28 +75,29 @@ range can be one of:
- 'deg' [ 0 - 360[
- (num) [ 0 - num["
- new: =>
- super 'num'
-
- setup: (inputs) =>
- { port, cc, chan, range } = match 'midi/port num num? any?', inputs
- super
- port: Input.io port
- cc: Input.value cc
- chan: Input.value chan or Value.num -1
- range: Input.value range or Value.str 'uni'
-
- if not @out\unwrap!
- @out\set apply_range @inputs.range, 0
-
- tick: =>
- { :port, :cc, :chan, :range } = @inputs
- if port\dirty!
- for msg in port!\receive!
- if msg.status == 'control-change' and
- (chan! == -1 or msg.chan == chan!) and
- msg.a == cc!
- @out\set apply_range range, msg.b
+ value: class extends Op
+ new: =>
+ super 'num'
+
+ setup: (inputs) =>
+ { port, cc, chan, range } = match 'midi/port num num? any?', inputs
+ super
+ port: Input.io port
+ cc: Input.value cc
+ chan: Input.value chan or Value.num -1
+ range: Input.value range or Value.str 'uni'
+
+ if not @out\unwrap!
+ @out\set apply_range @inputs.range, 0
+
+ tick: =>
+ { :port, :cc, :chan, :range } = @inputs
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.status == 'control-change' and
+ (chan! == -1 or msg.chan == chan!) and
+ msg.a == cc!
+ @out\set apply_range range, msg.b
{
:input
diff --git a/lib/midi/core.moon b/lib/midi/core.moon
index c437259..4986811 100644
--- a/lib/midi/core.moon
+++ b/lib/midi/core.moon
@@ -1,4 +1,4 @@
-import IO, Op, Registry, Input, Error, match from require 'core.base'
+import Value, IO, Op, Registry, Input, Error, match from require 'core.base'
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
bit = do
@@ -67,34 +67,46 @@ class PortOp extends Op
out = out and find_port RtMidiOut, out!
@out\set MidiPort inp, out
-class input extends PortOp
- @doc: "(midi/input name) - create a MIDI input port"
-
- setup: (inputs) =>
- { name } = match 'str', inputs
- super name: Input.value name
-
- tick: => super @inputs.name
-
-class output extends PortOp
- @doc: "(midi/output name) - create a MIDI output port"
-
- setup: (inputs) =>
- { name } = match 'str', inputs
- super name: Input.value name
-
- tick: => super nil, @inputs.name
-
-class inout extends PortOp
- @doc: "(midi/inout inname outname) - create a bidirectional MIDI port"
-
- setup: (inputs) =>
- { inp, out } = match 'str str', inputs
- super
- inp: Input.value inp
- out: Input.value out
-
- tick: => super @inputs.inp, @inputs.out
+input = Value.meta
+ meta:
+ name: 'input'
+ summary: "Create a MIDI input port."
+ examples: { '(midi/input name)' }
+
+ value: class extends PortOp
+ setup: (inputs) =>
+ { name } = match 'str', inputs
+ super name: Input.value name
+
+ tick: => super @inputs.name
+
+output = Value.meta
+ meta:
+ name: 'output'
+ summary: "Create a MIDI output port."
+ examples: { '(midi/output name)' }
+
+ value: class extends PortOp
+ setup: (inputs) =>
+ { name } = match 'str', inputs
+ super name: Input.value name
+
+ tick: => super nil, @inputs.name
+
+inout = Value.meta
+ meta:
+ name: 'inout'
+ summary: "Create a bidirectional MIDI port."
+ examples: { '(midi/inout name)' }
+
+ value: class extends PortOp
+ setup: (inputs) =>
+ { inp, out } = match 'str str', inputs
+ super
+ inp: Input.value inp
+ out: Input.value out
+
+ tick: => super @inputs.inp, @inputs.out
apply_range = (range, val) ->
if range\type! == 'str'
diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon
index 1084f66..37ccb82 100644
--- a/lib/midi/launchctl.moon
+++ b/lib/midi/launchctl.moon
@@ -5,9 +5,12 @@ import bor, lshift from bit
unpack or= table.unpack
color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4)
-class cc_seq extends Op
- @doc: "(launctl/cc-seq port i start chan [steps [range]]) - CC-Sequencer
-
+cc_seq = Value.meta
+ meta:
+ name: 'cc-seq'
+ summary: "MIDI CC-Sequencer."
+ examples: { '(launchctl/cc-seq port i start chan [steps [range]])' }
+ description: "
returns the value for the i-th step steps (buttons starting from start).
steps defaults to 8.
@@ -19,104 +22,109 @@ range can be one of:
- 'deg' [ 0 - 360[
- (num) [ 0 - num["
- new: => super 'num'
-
- setup: (inputs) =>
- { port, i, start,
- chan, steps, range } = match 'midi/port num num num num? any?', inputs
-
- super
- port: Input.io port
- i: Input.value i
- start: Input.value start
- chan: Input.value chan
- steps: Input.value steps or Value.num 8
- range: Input.value range or Value.str 'uni'
-
- if not @out\unwrap!
- @out\set apply_range @inputs.range, 0
-
- tick: =>
- { :port, :i, :start, :chan, :steps, :range } = @inputs
-
- if steps\dirty!
- while steps! > #@state
- table.insert @state, 0
- while steps! < #@state
- table.remove @state
-
- curr_i = i! % #@state
- if port\dirty!
- changed = false
- for msg in port!\receive!
- if msg.status == 'control-change' and msg.chan == chan!
- rel_i = msg.a - start!
- if rel_i >= 0 and rel_i < #@state
- @state[rel_i+1] = msg.b
- changed = rel_i == curr_i
- @out\set apply_range range, @state[curr_i+1] if changed
- else
- @out\set apply_range range, @state[curr_i+1]
-
-class gate_seq extends Op
- @doc: "(launctl/gate-seq port i start chan [steps]) - Gate-Sequencer
-
-returns true or false for the i-th step steps (buttons starting from start).
-steps defaults to 8."
-
- new: =>
- super 'bool', false
- @state = {}
-
- setup: (inputs) =>
- { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs
-
- super
- port: Input.io port
- i: Input.value i
- start: Input.value start
- chan: Input.value chan
- steps: Input.value steps or Value.num 8
-
- light = (set, active) ->
- set = if set then 'S' else ' '
- active = if active then 'A' else ' '
- color switch set .. active
- when ' ' then 0, 0
- when ' A' then 1, 1
- when 'S ' then 1, 0
- when 'SA' then 3, 1
-
- display: (i, active) =>
- { :port, :start, :chan } = @unwrap_all!
- port\send 'note-on', chan, (start + i), light @state[i+1], active
-
- tick: =>
- { :port, :i, :start, :chan, :steps } = @inputs
-
- if steps\dirty!
- while steps! > #@state
- table.insert @state, false
- while steps! < #@state
- table.remove @state
-
- curr_i = i! % #@state
-
- if port\dirty!
- for msg in port!\receive!
- if msg.status == 'note-on' and msg.chan == chan!
- rel_i = msg.a - start!
- if rel_i >= 0 and rel_i < #@state
- @state[rel_i+1] = not @state[rel_i+1]
- @display rel_i, rel_i == curr_i
-
- if i\dirty!
- prev_i = (curr_i - 1) % #@state
-
- @display curr_i, true
- @display prev_i, false
-
- @out\set @state[curr_i+1]
+ value: class extends Op
+ new: => super 'num'
+
+ setup: (inputs) =>
+ { port, i, start,
+ chan, steps, range } = match 'midi/port num num num num? any?', inputs
+
+ super
+ port: Input.io port
+ i: Input.value i
+ start: Input.value start
+ chan: Input.value chan
+ steps: Input.value steps or Value.num 8
+ range: Input.value range or Value.str 'uni'
+
+ if not @out\unwrap!
+ @out\set apply_range @inputs.range, 0
+
+ tick: =>
+ { :port, :i, :start, :chan, :steps, :range } = @inputs
+
+ if steps\dirty!
+ while steps! > #@state
+ table.insert @state, 0
+ while steps! < #@state
+ table.remove @state
+
+ curr_i = i! % #@state
+ if port\dirty!
+ changed = false
+ for msg in port!\receive!
+ if msg.status == 'control-change' and msg.chan == chan!
+ rel_i = msg.a - start!
+ if rel_i >= 0 and rel_i < #@state
+ @state[rel_i+1] = msg.b
+ changed = rel_i == curr_i
+ @out\set apply_range range, @state[curr_i+1] if changed
+ else
+ @out\set apply_range range, @state[curr_i+1]
+
+gate_seq = Value.meta
+ meta:
+ name: 'gate-seq'
+ summary: "MIDI Gate-Sequencer."
+ examples: { '(launchctl/gate-seq port i start chan [steps])' }
+ description: "
+returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from
+`start`). `steps` defaults to 8."
+
+ value: class extends Op
+ new: =>
+ super 'bool', false
+ @state = {}
+
+ setup: (inputs) =>
+ { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs
+
+ super
+ port: Input.io port
+ i: Input.value i
+ start: Input.value start
+ chan: Input.value chan
+ steps: Input.value steps or Value.num 8
+
+ light = (set, active) ->
+ set = if set then 'S' else ' '
+ active = if active then 'A' else ' '
+ color switch set .. active
+ when ' ' then 0, 0
+ when ' A' then 1, 1
+ when 'S ' then 1, 0
+ when 'SA' then 3, 1
+
+ display: (i, active) =>
+ { :port, :start, :chan } = @unwrap_all!
+ port\send 'note-on', chan, (start + i), light @state[i+1], active
+
+ tick: =>
+ { :port, :i, :start, :chan, :steps } = @inputs
+
+ if steps\dirty!
+ while steps! > #@state
+ table.insert @state, false
+ while steps! < #@state
+ table.remove @state
+
+ curr_i = i! % #@state
+
+ if port\dirty!
+ for msg in port!\receive!
+ if msg.status == 'note-on' and msg.chan == chan!
+ rel_i = msg.a - start!
+ if rel_i >= 0 and rel_i < #@state
+ @state[rel_i+1] = not @state[rel_i+1]
+ @display rel_i, rel_i == curr_i
+
+ if i\dirty!
+ prev_i = (curr_i - 1) % #@state
+
+ @display curr_i, true
+ @display prev_i, false
+
+ @out\set @state[curr_i+1]
{
'gate-seq': gate_seq
diff --git a/lib/osc.moon b/lib/osc.moon
index 101fb11..fe4eae4 100644
--- a/lib/osc.moon
+++ b/lib/osc.moon
@@ -1,64 +1,74 @@
-import Op, Input, match from require 'core.base'
+import Op, Value, Input, match from require 'core.base'
import pack from require 'osc'
import dns, udp from require 'socket'
unpack or= table.unpack
-class connect extends Op
- @doc: "(osc/connect host port) - UDP remote definition"
+connect = Value.meta
+ meta:
+ name: 'connect'
+ summary: "Create a UDP remote."
+ examples: { '(osc/connect host port)' }
- new: => super 'udp/socket'
+ value: class extends Op
+ new: => super 'udp/socket'
- setup: (inputs) =>
- { host, port } = match 'str num', inputs
- super
- host: Input.value host
- port: Input.value port
+ setup: (inputs) =>
+ { host, port } = match 'str num', inputs
+ super
+ host: Input.value host
+ port: Input.value port
- tick: =>
- { :host, :port } = @unwrap_all!
- ip = dns.toip host
+ tick: =>
+ { :host, :port } = @unwrap_all!
+ ip = dns.toip host
- @out\set with sock = udp!
- \setpeername ip, port
+ @out\set with sock = udp!
+ \setpeername ip, port
-class send extends Op
- @doc: "(osc/send socket path val) - send a value via OSC
+send = Value.meta
+ meta:
+ name: 'send'
+ summary: "Send a value via OSC."
+ examples: { '(osc/send socket path val)' }
+ description: "sends a message only when `val` is dirty."
-sends a message only when val is dirty."
+ value: class extends Op
+ setup: (inputs) =>
+ { socket, path, value } = match 'udp/socket str any', inputs
+ super
+ socket: Input.cold socket
+ path: Input.cold path
+ value: Input.value value
- setup: (inputs) =>
- { socket, path, value } = match 'udp/socket str any', inputs
- super
- socket: Input.cold socket
- path: Input.cold path
- value: Input.value value
+ tick: =>
+ if @inputs.value\dirty!
+ { :socket, :path, :value } = @unwrap_all!
+ msg = if 'table' == type value
+ pack path, unpack value
+ else
+ pack path, value
+ socket\send msg
- tick: =>
- if @inputs.value\dirty!
- { :socket, :path, :value } = @unwrap_all!
- msg = if 'table' == type value
- pack path, unpack value
- else
- pack path, value
- socket\send msg
-
-class send_state extends Op
- @doc: "(osc/send! socket path val) - synchronize a value via OSC
+send_state = Value.meta
+ meta:
+ name: 'send'
+ summary: "Synchronize a value via OSC."
+ examples: { '(osc/send! socket path val)' }
+ description: "sends a message whenever any parameter is dirty."
-sends a whenever any parameter changes."
+ value: class extends Op
+ setup: (inputs) =>
+ { socket, path, value } = match 'udp/socket str any', inputs
+ super
+ socket: Input.value socket
+ path: Input.value path
+ value: Input.value value
- setup: (inputs) =>
- { socket, path, value } = match 'udp/socket str any', inputs
- super
- socket: Input.value socket
- path: Input.value path
- value: Input.value value
-
- tick: =>
- { :socket, :path, :value } = @unwrap_all!
- msg = pack path, value
- socket\send msg
+ tick: =>
+ { :socket, :path, :value } = @unwrap_all!
+ msg = pack path, value
+ socket\send msg
{
:connect
diff --git a/lib/pilot.moon b/lib/pilot.moon
index a687e48..b851440 100644
--- a/lib/pilot.moon
+++ b/lib/pilot.moon
@@ -1,4 +1,4 @@
-import Op, Input, Error, match from require 'core.base'
+import Op, Value, Input, Error, match from require 'core.base'
import udp from require 'socket'
local conn
@@ -20,53 +20,65 @@ send = (...) ->
conn or= udp!
conn\sendto str, '127.0.0.1', 49161
-class play extends Op
- @doc: "(play trig ch oct note [vel [len]]) - play a note when trig is live"
+play = Value.meta
+ meta:
+ name: 'play'
+ summary: "Play a note when a bang arrives."
+ examples: { '(pilot/play trig ch oct note [vel [len]])' }
- setup: (inputs) =>
- { trig, args } = match 'bang *any', inputs
- assert #args < 6, Error 'argument', "too many arguments!"
- super
- trig: Input.event trig
- args: [Input.cold a for a in *args]
+ value: class extends Op
+ setup: (inputs) =>
+ { trig, args } = match 'bang *any', inputs
+ assert #args < 6, Error 'argument', "too many arguments!"
+ super
+ trig: Input.event trig
+ args: [Input.cold a for a in *args]
- tick: =>
- { :trig, :args } = @inputs
- if trig\dirty! and trig!
- send [a! for a in *@inputs.args]
+ tick: =>
+ { :trig, :args } = @inputs
+ if trig\dirty! and trig!
+ send [a! for a in *@inputs.args]
-class play_ extends Op
- @doc: "(play! ch oct note [vel [len]]) - play a note when note is live"
+play_ = Value.meta
+ meta:
+ name: 'play!'
+ summary: "Play a note when a note arrives."
+ examples: { '(pilot/play! ch oct note [vel [len]])' }
- setup: (inputs) =>
- { chan, octv, note, args } = match 'any any any *any', inputs
- assert #args < 3, Error 'argument', "too many arguments!"
- super
- chan: Input.cold chan
- octv: Input.cold octv
- note: Input.event note
- args: [Input.cold a for a in *args]
+ value: class extends Op
+ setup: (inputs) =>
+ { chan, octv, note, args } = match 'any any any *any', inputs
+ assert #args < 3, Error 'argument', "too many arguments!"
+ super
+ chan: Input.cold chan
+ octv: Input.cold octv
+ note: Input.event note
+ args: [Input.cold a for a in *args]
- tick: =>
- if @inputs.note\dirty!
- { :chan, :oct, :note, :args } = @unwrap_all!
- send { chan, oct, note }, args
+ tick: =>
+ if @inputs.note\dirty!
+ { :chan, :oct, :note, :args } = @unwrap_all!
+ send { chan, oct, note }, args
-class effect extends Op
- @doc: "(effect which a b) - set an effect
+effect = Value.meta
+ meta:
+ name: 'effect'
+ summary: "Set effect parameters."
+ examples: { '(pilot/effect which a b)' }
+ description: "`effect` should be one of 'DIS', 'CHO', 'REV' or 'FEE'"
-which is one of 'DIS', 'CHO', 'REV' or 'FEE'"
+ value: class extends Op
+ setup: (inputs) =>
+ { which, a, b } = match 'str num num', inputs
+ super {
+ Input.cold which
+ Input.value a
+ Input.value b
+ }
- setup: (inputs) =>
- { which, a, b } = match 'str num num', inputs
- super {
- Input.cold which
- Input.value a
- Input.value b
- }
+ tick: =>
+ send @unwrap_all!
- tick: =>
- send @unwrap_all!
{
:play
'play!': play_
diff --git a/lib/random.moon b/lib/random.moon
index b97671d..a3124fa 100644
--- a/lib/random.moon
+++ b/lib/random.moon
@@ -23,38 +23,20 @@ range can be one of:
- (num) [ 0 - num["
class num extends Op
- @doc: "(random/num [trigger] [range]) - create a random number
-
-generates a random value in range on create and trigger.
+num = Value.meta
+ meta:
+ name: 'num'
+ summary: 'Generate a random number.'
+ examples: { '(random/num [trigger] [range]))' }
+ description: "generate a random value in `range` when created and on `trig`.
#{range_doc}"
- new: =>
- super 'num'
- @gen!
-
- gen: => @state = { math.random! }
-
- setup: (inputs) =>
- { trig, range } = match 'bang? any?', inputs
- super
- trig: trig and Input.event trig
- range: Input.value range or Value.str 'uni'
-
- tick: =>
- @gen! if @inputs.trig and @inputs.trig\dirty!
- @out\set apply_range @inputs.range, @state[1]
-vec_ = (n) ->
- class vec extends Op
- @doc: "(random/vec#{n} [trigger] [range]) - create a random number
-
-generates a random vec#{n} on create and trigger.
-each component is in range.
-#{range_doc}"
+ value: class extends Op
new: =>
- super "vec#{n}"
+ super 'num'
@gen!
- gen: => @state = for i=1,n do math.random!
+ gen: => @state = { math.random! }
setup: (inputs) =>
{ trig, range } = match 'bang? any?', inputs
@@ -64,10 +46,33 @@ each component is in range.
tick: =>
@gen! if @inputs.trig and @inputs.trig\dirty!
- @out\set [apply_range @inputs.range, v for v in *@state]
+ @out\set apply_range @inputs.range, @state[1]
+
+vec_ = (n) ->
+ Value.meta
+ meta:
+ name: "vec#{n}"
+ summary: 'Generate a random vector.'
+ examples: { '(random/vec#{n} [trigger] [range]))' }
+ description: "generate a random vec#{n} in `range` when created and on `trig`.
+#{range_doc}"
+
+ value: class extends Op
+ new: =>
+ super "vec#{n}"
+ @gen!
+
+ gen: => @state = for i=1,n do math.random!
+
+ setup: (inputs) =>
+ { trig, range } = match 'bang? any?', inputs
+ super
+ trig: trig and Input.event trig
+ range: Input.value range or Value.str 'uni'
- vec.__name = "vec#{n}"
- vec
+ tick: =>
+ @gen! if @inputs.trig and @inputs.trig\dirty!
+ @out\set [apply_range @inputs.range, v for v in *@state]
{
:num
diff --git a/lib/sc.moon b/lib/sc.moon
index 7562620..dd9e826 100644
--- a/lib/sc.moon
+++ b/lib/sc.moon
@@ -1,30 +1,37 @@
-import Op, Input, Error, match from require 'core.base'
+import Op, Value, Input, Error, match from require 'core.base'
import pack from require 'osc'
import dns, udp from require 'socket'
-class play extends Op
- @doc: "(sc/play socket synth trigger [name-str val]...) - play a SC SynthDef"
+play = Value.meta
+ meta:
+ name: 'play'
+ summary: 'Play a SuperCollider SynthDef.'
+ examples: { '(play socket synth trig [param val…])' }
+ description: "
+Plays the synth `synth` on the `udp/socket` `socket` whenever `trig` is live.
+Any number of parameter-value pairs can be specified and are captured and sent
+together with the note when triggered."
+ value: class extends Op
+ setup: (inputs) =>
+ { socket, synth, trig, ctrls } = match 'udp/socket str bang *any?', inputs
- setup: (inputs) =>
- { socket, synth, trig, ctrls } = match 'udp/socket str bang *any?', inputs
+ assert #ctrls % 2 == 0, Error 'argument', "parameters need to be specified as pairs"
+ for key in *ctrls[1,,2]
+ assert key\type! == 'str', Error 'argument', "ony strings are supported as control names"
+ for val in *ctrls[2,,2]
+ assert val\type! == 'num', Error 'argument', "only numbers are supported as control values"
- assert #ctrls % 2 == 0, Error 'argument', "parameters need to be specified as pairs"
- for key in *ctrls[1,,2]
- assert key\type! == 'str', Error 'argument', "ony strings are supported as control names"
- for val in *ctrls[2,,2]
- assert val\type! == 'num', Error 'argument', "only numbers are supported as control values"
+ super
+ trig: Input.event trig
+ socket: Input.cold socket
+ synth: Input.cold synth
+ ctrls: [Input.cold v for v in *ctrls]
- super
- trig: Input.event trig
- socket: Input.cold socket
- synth: Input.cold synth
- ctrls: [Input.cold v for v in *ctrls]
-
- tick: =>
- if @inputs.trig\dirty! and @inputs.trig!
- { :socket, :synth, :ctrls } = @unwrap_all!
- msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls
- socket\send msg
+ tick: =>
+ if @inputs.trig\dirty! and @inputs.trig!
+ { :socket, :synth, :ctrls } = @unwrap_all!
+ msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls
+ socket\send msg
{
:play
diff --git a/lib/string.moon b/lib/string.moon
index 06509f3..8b48977 100644
--- a/lib/string.moon
+++ b/lib/string.moon
@@ -1,12 +1,15 @@
-import Op, Input from require 'core.base'
+import Op, Value, Input from require 'core.base'
-class str extends Op
- @doc: "(str v1 [v2]...)
-(.. v1 [v2]...) - concatenate/stringify values"
- new: => super 'str'
+str = Value.meta
+ meta:
+ name: 'str'
+ summary: "Concatenate/stringify values."
+ examples: { '(.. v1 [v2…])', '(str v1 [v2…])' }
+ value: class extends Op
+ new: => super 'str'
- setup: (inputs) => super [Input.value v for v in *inputs]
- tick: => @out\set table.concat [tostring v! for v in *@inputs]
+ setup: (inputs) => super [Input.value v for v in *inputs]
+ tick: => @out\set table.concat [tostring v! for v in *@inputs]
{
:str, '..': str
diff --git a/lib/time.moon b/lib/time.moon
index 8e9aded..182cf4d 100644
--- a/lib/time.moon
+++ b/lib/time.moon
@@ -1,5 +1,4 @@
-import Registry, Value, IO, Op, Input, match
- from require 'core.base'
+import Registry, Value, IO, Op, Input, match from require 'core.base'
import monotime from require 'system'
class Clock extends IO
@@ -20,131 +19,148 @@ class Clock extends IO
dirty: => @is_dirty
-class clock extends Op
- @doc: "(clock [fps]) - a clock source
-
+clock = Value.meta
+ meta:
+ name: 'clock'
+ summary: "Create a clock source."
+ examples: { '(clock)', '(clock fps)' }
+ description: "
IO that triggers other operators at a fixed frame rate.
-fps defaults to 60 and has to be an eval-time constant"
-
- new: => super 'clock'
-
- setup: (inputs) =>
- { fps } = match 'num?', inputs
- super fps: Input.value fps or Value.num 60
-
- tick: =>
- if @inputs.fps\dirty!
- @out\set Clock 1 / @inputs.fps!
-
-class lfo extends Op
- @doc: "(lfo [clock] freq [wave]) - low-frequency oscillator
-
+`fps` defaults to 60 and has to be an eval-time constant"
+ value: class extends Op
+ new: => super 'clock'
+
+ setup: (inputs) =>
+ { fps } = match 'num?', inputs
+ super fps: Input.value fps or Value.num 60
+
+ tick: =>
+ if @inputs.fps\dirty!
+ @out\set Clock 1 / @inputs.fps!
+
+lfo = Value.meta
+ meta:
+ name: 'lfo'
+ summary: "Low-frequency oscillator."
+ examples: { '(lfo [clock] freq wave)' }
+ description: "
oscillates between 0 and 1 at the frequency freq.
wave selects the wave shape from the following:
-- sin (default)
-- saw
-- tri"
-
- new: =>
- super 'num'
- @state.phase or= 0
-
- default_wave = Value.str 'sin'
- setup: (inputs, scope) =>
- { clock, freq, wave } = match 'clock? num any?', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- freq: Input.value freq
- wave: Input.value wave or default_wave
-
- tau = math.pi * 2
- tick: =>
- if @inputs.clock\dirty!
- { :clock, :freq, :wave } = @unwrap_all!
-
- @state.phase += clock.dt * freq
- @out\set switch wave
- when 'sin' then .5 + .5 * math.cos @state.phase * tau
- when 'saw' then @state.phase % 1
- when 'tri' then math.abs (2*@state.phase % 2) - 1
- else error "unknown wave type"
-
-class ramp extends Op
- @doc: "(ramp [clock] period [max]) - sawtooth lfo
-
+- `'sin'` (default)
+- `'saw'`
+- `'tri'`"
+ value: class extends Op
+ new: =>
+ super 'num'
+ @state.phase or= 0
+
+ default_wave = Value.str 'sin'
+ setup: (inputs, scope) =>
+ { clock, freq, wave } = match 'clock? num any?', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ freq: Input.value freq
+ wave: Input.value wave or default_wave
+
+ tau = math.pi * 2
+ tick: =>
+ if @inputs.clock\dirty!
+ { :clock, :freq, :wave } = @unwrap_all!
+
+ @state.phase += clock.dt * freq
+ @out\set switch wave
+ when 'sin' then .5 + .5 * math.cos @state.phase * tau
+ when 'saw' then @state.phase % 1
+ when 'tri' then math.abs (2*@state.phase % 2) - 1
+ else error "unknown wave type"
+
+ramp = Value.meta
+ meta:
+ name: 'ramp'
+ summary: "Sawtooth LFO."
+ examples: { '(ramp [clock] period [max])' }
+ description: "
ramps from 0 to max (default same as ramp) once every period seconds."
-
- new: =>
- super 'num'
- @state.phase or= 0
-
- setup: (inputs, scope) =>
- { clock, period, max } = match 'clock? num num?', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- period: Input.value period
- max: max and Input.value max
-
- tick: =>
- clock_dirty = @inputs.clock\dirty!
- if clock_dirty
- { :clock, :period, :max } = @unwrap_all!
- max or= period
- @state.phase += clock.dt / period
-
- while @state.phase >= 1
- @state.phase -= 1
-
- if clock_dirty or (@inputs.max and @inputs.max\dirty!)
- @out\set @state.phase * max
-
-class tick extends Op
- @doc: "(tick [clock] period) - count ticks
-
-counts upwards by one every period seconds and returns the number of completed ticks."
- new: =>
- super 'num', 0
- @state.phase or= 0
- @state.count or= 0
-
- setup: (inputs, scope) =>
- { clock, period } = match 'clock? num', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- period: Input.value period
-
- tick: =>
- if @inputs.clock\dirty!
- { :clock, :period, :max } = @unwrap_all!
- @state.phase += clock.dt / period
-
- while @state.phase >= 1
- @state.phase -= 1
- @state.count += 1
- @out\set @state.count
-
-class every extends Op
- @doc: "(every [clock] period) - trigger every period seconds
-
-returns true once every period seconds."
- new: =>
- super 'bang'
- @state.phase or= 0
-
- setup: (inputs, scope) =>
- { clock, period } = match 'clock? num', inputs
- super
- clock: Input.io clock or scope\get '*clock*'
- period: Input.value period
-
- tick: =>
- if @inputs.clock\dirty!
- { :clock, :period, :max } = @unwrap_all!
- @state.phase += clock.dt / period
-
- while @state.phase >= 1
- @state.phase -= 1
- @out\set true
+ value: class extends Op
+ new: =>
+ super 'num'
+ @state.phase or= 0
+
+ setup: (inputs, scope) =>
+ { clock, period, max } = match 'clock? num num?', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ period: Input.value period
+ max: max and Input.value max
+
+ tick: =>
+ clock_dirty = @inputs.clock\dirty!
+ if clock_dirty
+ { :clock, :period, :max } = @unwrap_all!
+ max or= period
+ @state.phase += clock.dt / period
+
+ while @state.phase >= 1
+ @state.phase -= 1
+
+ if clock_dirty or (@inputs.max and @inputs.max\dirty!)
+ @out\set @state.phase * max
+
+tick = Value.meta
+ meta:
+ name: 'tick'
+ summary: "Count ticks."
+ examples: { '(tick [clock] period)' }
+ description: "
+counts upwards by one every period seconds and returns the number of completed
+ticks."
+ value: class extends Op
+ new: =>
+ super 'num', 0
+ @state.phase or= 0
+ @state.count or= 0
+
+ setup: (inputs, scope) =>
+ { clock, period } = match 'clock? num', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ period: Input.value period
+
+ tick: =>
+ if @inputs.clock\dirty!
+ { :clock, :period, :max } = @unwrap_all!
+ @state.phase += clock.dt / period
+
+ while @state.phase >= 1
+ @state.phase -= 1
+ @state.count += 1
+ @out\set @state.count
+
+every = Value.meta
+ meta:
+ name: 'every'
+ summary: "Emit bangs."
+ examples: { '(every [clock] period)' }
+ description: "returns true once every period seconds."
+ value: class extends Op
+ new: =>
+ super 'bang'
+ @state.phase or= 0
+
+ setup: (inputs, scope) =>
+ { clock, period } = match 'clock? num', inputs
+ super
+ clock: Input.io clock or scope\get '*clock*'
+ period: Input.value period
+
+ tick: =>
+ if @inputs.clock\dirty!
+ { :clock, :period, :max } = @unwrap_all!
+ @state.phase += clock.dt / period
+
+ while @state.phase >= 1
+ @state.phase -= 1
+ @out\set true
{
:clock
@@ -152,5 +168,8 @@ returns true once every period seconds."
:ramp
:tick
:every
- '*clock*': Value 'clock', Clock 1/60
+ '*clock*': with Value 'clock', Clock 1/60
+ .meta =
+ name: '*clock*'
+ summary: 'Default clock source (60fps).'
}
diff --git a/lib/util.moon b/lib/util.moon
index f816eee..a2c65ba 100644
--- a/lib/util.moon
+++ b/lib/util.moon
@@ -7,106 +7,100 @@ all_same = (list) ->
list[1]
-class switch_ extends Op
- @doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs
-
-when i is true, the first value is reproduced.
-when i is false, the second value is reproduced.
-when i is a num, it is (floor)ed and the matching argument (starting from 0) is reproduced."
-
- setup: (inputs) =>
- { i, values } = match 'any *any', inputs
-
- i_type = i\type!
- assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
- typ = all_same [v\type! for v in *values]
- @out = Value typ if not @out or typ != @out.type
-
- super
- i: Input.value i
- values: [Input.value v for v in *values]
-
- tick: =>
- { :i, :values } = @inputs
- active = switch i!
- when true
- values[1]
- when false
- values[2]
- else
- i = 1 + (math.floor i!) % #values
- values[i]
- @out\set active and active!
-
-class route extends Op
- @doc: "(route i v0 [v1 v2...]) - route between multiple inputs
-
-when i is true, the first value is reproduced.
-when i is false, the second value is reproduced.
-when i is a num, it is (floor)ed and the matching argument (starting from 0) is reproduced."
-
- setup: (inputs) =>
- { i, values } = match 'any *any', inputs
-
- i_type = i\type!
- assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
- typ = all_same [v\type! for v in *values]
- @out = Value typ if not @out or typ != @out.type
-
- super
- i: Input.value i
- values: [Input.value v for v in *values]
-
- tick: =>
- { :i, :values } = @inputs
- active = switch i!
- when true
- values[1]
- when false
- values[2]
- else
- i = 1 + (math.floor i!) % #values
- values[i]
- if active and active\dirty!
- @out\set active!
-
-class edge extends Op
- @doc: "(edge bool) - convert rising edges to bangs"
- new: => super 'bang'
-
- setup: (inputs) =>
- { value } = match 'bool', inputs
- super value: Input.value value
-
- tick: =>
- now = @inputs.value!
- if now and not @state.last
- @out\set true
- @state.last = now
-
-class default extends Op
- @doc: "(default stream default) - provide a default value for an event stream
-
-starts out as default but forwards events from stream.
-default defaults to zero."
-
- setup: (params) =>
- { value, init } = match 'any any', inputs
- super
- value: Input.event value
- init: Input.cold init
-
- @out = Value value\type!
- @out\set @inputs.init\unwrap!
-
- tick: =>
- { :value } = @inputs
- if value\dirty!
- @out\set value!
+switch_ = Value.meta
+ meta:
+ name: 'switch'
+ summary: "Switch between multiple inputs."
+ examples: { '(switch i v0 [v1 v2…])' }
+ description: "
+- when `i` is `true`, the first value is reproduced.
+- when `i` is `false`, the second value is reproduced.
+- when `i` is a `num`, it is [math/floor][]ed and the matching argument
+ (indexed starting from 0) is reproduced."
+
+ value: class extends Op
+ setup: (inputs) =>
+ { i, values } = match 'any *any', inputs
+
+ i_type = i\type!
+ assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
+ typ = all_same [v\type! for v in *values]
+ @out = Value typ if not @out or typ != @out.type
+
+ super
+ i: Input.value i
+ values: [Input.value v for v in *values]
+
+ tick: =>
+ { :i, :values } = @inputs
+ active = switch i!
+ when true
+ values[1]
+ when false
+ values[2]
+ else
+ i = 1 + (math.floor i!) % #values
+ values[i]
+ @out\set active and active!
+
+route = Value.meta
+ meta:
+ name: 'route'
+ summary: "Route between multiple inputs."
+ examples: { '(route i v0 [e1 e2…])' }
+ description: "
+- when `i` is `true`, the first event stream is reproduced.
+- when `i` is `false`, the second event stream is reproduced.
+- when `i` is a `num`, it is [math/floor][]ed and the matching argument
+ (indexed starting from 0) is reproduced."
+
+ value: class extends Op
+ setup: (inputs) =>
+ { i, values } = match 'any *any', inputs
+
+ i_type = i\type!
+ assert i_type == 'bool' or i_type == 'num', Error 'argument', "i has to be bool or num"
+ typ = all_same [v\type! for v in *values]
+ @out = Value typ if not @out or typ != @out.type
+
+ super
+ i: Input.value i
+ values: [Input.value v for v in *values]
+
+ tick: =>
+ { :i, :values } = @inputs
+ active = switch i!
+ when true
+ values[1]
+ when false
+ values[2]
+ else
+ i = 1 + (math.floor i!) % #values
+ values[i]
+ if active and active\dirty!
+ @out\set active!
+
+route = Value.meta
+ meta:
+ name: 'edge'
+ summary: "Convert rising edges to bangs."
+ examples: { '(edge bool)' }
+
+ value: class extends Op
+ new: => super 'bang'
+
+ setup: (inputs) =>
+ { value } = match 'bool', inputs
+ super value: Input.value value
+
+ tick: =>
+ now = @inputs.value!
+ if now and not @state.last
+ @out\set true
+ @state.last = now
{
'switch': switch_
:route
:edge
- :default
}