aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-02 11:41:33 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-02 11:41:33 +0000
commit03853b35a473161f377fb74a05723cacc5cbf36b (patch)
tree2e3df044cc0ff3ed24a07d6b5ee31682f077e5fa
parentnew op interface part one (diff)
downloadalive-03853b35a473161f377fb74a05723cacc5cbf36b.tar.gz
alive-03853b35a473161f377fb74a05723cacc5cbf36b.zip
IO system
-rw-r--r--core/base.moon25
-rw-r--r--core/init.moon8
-rw-r--r--core/pattern.moon11
-rw-r--r--core/registry.moon8
-rw-r--r--core/value.moon6
-rw-r--r--lib/debug.moon4
-rw-r--r--lib/midi/core.moon77
-rw-r--r--lib/midi/init.moon39
-rw-r--r--lib/midi/launchctl.moon102
-rw-r--r--lib/time.moon5
10 files changed, 154 insertions, 131 deletions
diff --git a/core/base.moon b/core/base.moon
index 0a88cc1..6af4913 100644
--- a/core/base.moon
+++ b/core/base.moon
@@ -5,6 +5,7 @@ unpack or= table.unpack
class Input
new: (value) =>
+ assert value, "nil passed to Input: #{value}"
@stream = switch value.__class
when Result
assert value.value, "Input from result without value!"
@@ -19,8 +20,12 @@ class Input
dirty: => @stream\dirty!
unwrap: => @stream\unwrap!
type: => @stream.type
+
__call: => @stream\unwrap!
- __inherited: (cls) => cls.__base.__call = @__call
+ __tostring: => "#{@@__name}:#{@stream}"
+ __inherited: (cls) =>
+ cls.__base.__call = @__call
+ cls.__base.__tostring = @__tostring
-- ValueInput scheduling policy
--
@@ -35,6 +40,19 @@ class ValueInput extends Input
-- only marked dirty if the input stream itself is dirty
class EventInput extends Input
+-- IOInput scheduling policy
+--
+-- lifts streams of IO objects to events
+class IOInput extends Input
+ dirty: => @stream\unwrap!\dirty!
+
+class IO
+ -- called in the main event loop
+ tick: =>
+
+ -- whether a tree update is necessary
+ dirty: =>
+
-- a persistent expression Operator
--
-- accepts Const or Stream inputs and produces a Stream output
@@ -61,7 +79,7 @@ class Op
if cur_plain and old_plain
-- both are tables, recurse
do_merge old_val, cur_val
- elseif cur_plain == old_plain
+ elseif not (cur_plain or old_plain)
-- both are streams (or nil), merge them
cur_val\merge old_val
@@ -191,8 +209,9 @@ class FnDef
"(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
{
- :ValueInput, :EventInput
+ :ValueInput, :EventInput, :IOInput
:Dispatcher
+ :IO
:Op
:Action
:FnDef
diff --git a/core/init.moon b/core/init.moon
index a309722..6b5bdd0 100644
--- a/core/init.moon
+++ b/core/init.moon
@@ -1,6 +1,8 @@
L or= setmetatable {}, __index: => ->
-import Op, Action, FnDef from require 'core.base'
+import Op, IO, Action, FnDef, EventInput, ValueInput, IOInput
+ from require 'core.base'
+import match from require 'core.pattern'
import Value, Result, load_ from require 'core.value'
import Scope from require 'core.scope'
@@ -17,7 +19,9 @@ globals = Scope.from_table require 'core.builtin'
{
:Value, :Result
:Cell, :RootCell
- :Op, :Action, :FnDef
+
+ :Op, :IO, :Action, :FnDef
+ :EventInput, :ValueInput, :IOInput, :match
:Scope
:Registry, :Tag
diff --git a/core/pattern.moon b/core/pattern.moon
index 64b53b8..13497af 100644
--- a/core/pattern.moon
+++ b/core/pattern.moon
@@ -35,13 +35,20 @@ class Pattern
matched = while @matches results[1]
table.remove results, 1
- assert @opt or #matched > 0, "expected at least one argument for spread!"
+ assert @opt or #matched > 0, "expected at least one argument for spread"
matched
else
matches = @matches results[1]
- assert @opt or matches, "couldn't match argument #{results[1]} as type #{@type}!"
+ assert @opt or matches, "couldn't match argument #{results[1]} as #{@}"
if matches then table.remove results, 1
+ __tostring: =>
+ str = @type
+ str = '*' .. str if @splat
+ str = '=' .. str if @const
+ str = str .. '?' if @opt
+ str
+
match = (pattern, results) ->
patterns = while pattern
pat, rest = pattern\match '^([^ ]+) (.*)$'
diff --git a/core/registry.moon b/core/registry.moon
index 2a83f7e..ac1943a 100644
--- a/core/registry.moon
+++ b/core/registry.moon
@@ -3,6 +3,7 @@ import Result, Value from require 'core.value'
class Registry
new: () =>
@map = {}
+ @io = {}
@tick = 0
@kr = Result value: Value.bool true
@@ -22,6 +23,10 @@ class Registry
active: -> assert Registry.active_registry, "no active Registry!"
+-- IO
+ add_io: (io) => @io[io] = true
+ remove_io: (io) => @io[io] = nil
+
-- public methods
wrap_eval: (fn) => (...) ->
@@ -50,6 +55,9 @@ class Registry
@tick += 1
@kr.value\set true
+ for io in pairs @io
+ io\tick!
+
with fn ...
@release!
diff --git a/core/value.moon b/core/value.moon
index 0d44320..0e72486 100644
--- a/core/value.moon
+++ b/core/value.moon
@@ -36,8 +36,8 @@ class Result
if @op
for input in @op\all_inputs!
- continue if is_child[input]
- @side_inputs[input] = true
+ continue if is_child[input.stream]
+ @side_inputs[input.stream] = true
is_const: => not next @side_inputs
@@ -97,7 +97,7 @@ class Value
-- * scope, opdef, fndef, builtin
-- @value - Lua value - access through :unwrap()
new: (@type, @value, @raw) =>
- @updated = 0
+ @updated = nil
dirty: => @updated == Registry.active!.tick
diff --git a/lib/debug.moon b/lib/debug.moon
index 1457cee..e098800 100644
--- a/lib/debug.moon
+++ b/lib/debug.moon
@@ -1,6 +1,4 @@
-import Op from require 'core'
-import ValueInput, EventInput from require 'core.base'
-import match from require 'core.pattern'
+import Op, ValueInput, EventInput, match from require 'core'
class out extends Op
@doc: "(out [name-str?] value) - log value to the console"
diff --git a/lib/midi/core.moon b/lib/midi/core.moon
index 3bf47a9..e0a3334 100644
--- a/lib/midi/core.moon
+++ b/lib/midi/core.moon
@@ -1,8 +1,6 @@
+import IO, Op, Registry, ValueInput, match from require 'core'
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
import band, bor, lshift, rshift from require 'bit32'
-import Op, Registry from require 'core'
-import ValueInput, EventInput from require 'core.base'
-import match from require 'core.pattern'
MIDI = {
[0x9]: 'note-on'
@@ -28,11 +26,9 @@ find_port = (Klass, name) ->
\openport id
-class MidiPort
+class MidiPort extends IO
new: (@inp, @out) =>
-
- dirty: =>
- @updated == Registry.active!.tick
+ @messages = {}
tick: =>
if @inp
@@ -43,76 +39,65 @@ class MidiPort
{ status, a, b } = bytes
chan = band status, 0xf
status = MIDI[rshift status, 4]
- { :status, :chan, :a, :b }
+ { :status, :chan, :a, :b, port: @ }
- if @messages
- @updated = Registry.active!.tick
+ dirty: => #@messages > 0
receive: =>
- assert @inp, "#{@} is not an input port"
- return unless @messages
coroutine.wrap ->
for msg in *@messages
coroutine.yield msg
send: (status, chan, a, b) =>
- assert @out, "#{@} is not an output port"
+ assert @out, "#{@} is not an output or bidirectional port"
if 'string' == type 'status'
status = bor (lshift rMIDI[status], 4), chan
@out\sendmessage status, a, b
-class input extends Op
- @doc: "(midi/input name) - create a MIDI input port"
-
+class PortOp extends Op
new: => super 'midi/port'
+ destroy: =>
+ Registry.active!\remove_io @port if @port
+
+ tick: (inp, out) =>
+ if (inp and inp\dirty!) or (out and out\dirty!)
+ Registry.active!\remove_io @port if @port
+ inp = inp and find_port RtMidiIn, inp!
+ out = out and find_port RtMidiOut, out!
+ @port = MidiPort inp, out
+ Registry.active!\add_io @port
+
+ @out\set @port
+
+class input extends PortOp
+ @doc: "(midi/input name) - create a MIDI input port"
+
setup: (inputs) =>
{ name } = match 'str', inputs
- super
- name: ValueInput name
- root: EventInput Registry.active!.kr
-
- tick: =>
- if @inputs.name\dirty!
- @out\set MidiPort find_port RtMidiIn, @inputs.name!
+ super name: ValueInput name
- @out\unwrap!\tick!
+ tick: => super @inputs.name
-class output extends Op
+class output extends PortOp
@doc: "(midi/output name) - create a MIDI output port"
- new: => super 'midi/port'
-
setup: (inputs) =>
{ name } = match 'str', inputs
- super
- name: ValueInput name
- root: EventInput Registry.active!.kr
-
- tick: =>
- if @inputs.name\dirty!
- @out\set MidiPort nil, find_port RtMidiOut, @inputs.name!
+ super name: ValueInput name
- @out\unwrap!\tick!
+ tick: => super nil, @inputs.name
-class inout extends Op
+class inout extends PortOp
@doc: "(midi/inout inname outname) - create a bidirectional MIDI port"
- new: => super 'midi/port'
-
setup: (inputs) =>
- { inp, out } = match 'str, str', inputs
+ { inp, out } = match 'str str', inputs
super
inp: ValueInput inp
out: ValueInput out
- root: EventInput Registry.active!.kr
-
- tick: =>
- { :inp, :out } = @inputs
- if inp\dirty! or out\dirty!
- @out\set MidiPort (find_port RtMidiIn, inp!), (find_port RtMidiOut, out!)
- @out\unwrap!\tick!
+ tick: => super @inputs.inp, @inputs.out
apply_range = (range, val) ->
if range\type! == 'str'
diff --git a/lib/midi/init.moon b/lib/midi/init.moon
index 0cbe5e0..3bfba48 100644
--- a/lib/midi/init.moon
+++ b/lib/midi/init.moon
@@ -1,7 +1,5 @@
-import Value, Op from require 'core'
+import Value, Op, ValueInput, IOInput, match from require 'core'
import input, output, inout, apply_range from require 'lib.midi.core'
-import ValueInput, EventInput from require 'core.base'
-import match from require 'core.pattern'
class gate extends Op
@doc: "(midi/gate port note [chan]) - gate from note-on and note-off messages"
@@ -10,9 +8,9 @@ class gate extends Op
super 'bool', false
setup: (inputs) =>
- { port, note, chan } = match '=midi/port num num?', inputs
+ { :port, :note, :chan } = match 'midi/port num num?', inputs
super
- port: EventInput port.value!
+ port: IOInput port
note: ValueInput note
chan: ValueInput chan or Value.num -1
@@ -22,12 +20,13 @@ class gate extends Op
if note\dirty! or chan\dirty!
@out\set false
- 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
+ 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 cc extends Op
@doc: "(midi/cc port cc [chan [range]]) - MIDI CC to number
@@ -42,11 +41,10 @@ range can be one of:
new: =>
super 'num'
-
setup: (inputs) =>
- { port, cc, chan, range } = match '=midi/port num num? any?', inputs
+ { port, cc, chan, range } = match 'midi/port num num? any?', inputs
super
- port: EventInput port.value!
+ port: IOInput port
cc: ValueInput cc
chan: ValueInput chan or Value.num -1
range: ValueInput range or Value.str 'uni'
@@ -55,12 +53,13 @@ range can be one of:
@out\set apply_range @inputs.range, 0
tick: =>
- { port, cc, chan, range } = @inputs
- 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
+ { :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/launchctl.moon b/lib/midi/launchctl.moon
index aab20bd..b3bf5da 100644
--- a/lib/midi/launchctl.moon
+++ b/lib/midi/launchctl.moon
@@ -1,4 +1,4 @@
-import Value, Op from require 'core'
+import Value, Op, ValueInput, EventInput, IOInput, match from require 'core'
import apply_range from require 'lib.midi.core'
import bor, lshift from require 'bit32'
@@ -22,42 +22,42 @@ range can be one of:
super 'num'
@steps = {}
- setup: (params) =>
- super params
+ setup: (inputs) =>
+ { port, i, start,
+ chan, steps, range } = match 'midi/port num num num num? any?', inputs
- @inputs[5] or= Value.num 8
- @inputs[6] or= Value.str 'uni'
- assert #@inputs == 6
- assert @inputs[6].type == 'num' or @inputs[6].type == 'str'
- @assert_first_types 'midi/port', 'num', 'num', 'num', 'num'
- @impulses = { @inputs[1]\unwrap! }
+ super
+ port: IOInput port
+ i: ValueInput i
+ start: ValueInput start
+ chan: ValueInput chan
+ steps: ValueInput steps or Value.num 8
+ range: ValueInput range or Value.str 'uni'
if not @out\unwrap!
- @out\set apply_range @inputs[6], 0
+ @out\set apply_range @inputs.range, 0
- tick: (first) =>
- port = @inputs[1]\unwrap!
- _, i, start, chan, steps = unpack @inputs
+ tick: =>
+ { :port, :i, :start, :chan, :steps, :range } = @inputs
- if first or @inputs[5]\dirty!
- steps = @inputs[5]!
- while steps > #@steps
+ if steps\dirty!
+ while steps! > #@steps
table.insert @steps, 0
- while steps < #@steps
+ while steps! < #@steps
table.remove @steps
- curr_i = i\unwrap! % #@steps
- 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 < #@steps
- @steps[rel_i+1] = msg.b
- changed = rel_i == curr_i
-
- if changed or i\dirty! or start\dirty! or chan\dirty! or steps\dirty!
- @out\set apply_range @inputs[6], @steps[curr_i+1]
+ curr_i = i! % #@steps
+ 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 < #@steps
+ @steps[rel_i+1] = msg.b
+ changed = rel_i == curr_i
+ @out\set apply_range range, @steps[curr_i+1] if changed
+ else
+ @out\set apply_range range, @steps[curr_i+1]
class gate_seq extends Op
@doc: "(launctl/gate-seq port i start chan [steps]) - Gate-Sequencer
@@ -69,13 +69,15 @@ steps defaults to 8."
super 'bool', false
@steps = {}
- setup: (params) =>
- super params
+ setup: (inputs) =>
+ { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs
- @inputs[5] or= Value.num 8
- @inputs[6] or= Value.str 'uni'
- @assert_types 'midi/port', 'num', 'num', 'num', 'num', 'str'
- @impulses = { @inputs[1]\unwrap! }
+ super
+ port: IOInput port
+ i: ValueInput i
+ start: ValueInput start
+ chan: ValueInput chan
+ steps: ValueInput steps or Value.num 8
light = (set, active) ->
set = if set then 'S' else ' '
@@ -85,29 +87,31 @@ steps defaults to 8."
when ' A' then 1, 1
when 'S ' then 1, 0
when 'SA' then 3, 1
+
display: (i, active) =>
- port, _, start, chan = @unwrap_inputs!
+ { :port, :start, :chan } = @unwrap_all!
port\send 'note-on', chan, (start + i), light @steps[i+1], active
- tick: (first) =>
- port, curr_i, start, chan, steps = @unwrap_inputs!
+ tick: =>
+ { :port, :i, :start, :chan, :steps } = @inputs
- if first or @inputs[5]\dirty!
- while steps > #@steps
+ if steps\dirty!
+ while steps! > #@steps
table.insert @steps, false
- while steps < #@steps
+ while steps! < #@steps
table.remove @steps
- curr_i = curr_i % #@steps
+ curr_i = i! % #@steps
- 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 < #@steps
- @steps[rel_i+1] = not @steps[rel_i+1]
- @display rel_i, rel_i == curr_i
+ 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 < #@steps
+ @steps[rel_i+1] = not @steps[rel_i+1]
+ @display rel_i, rel_i == curr_i
- if @inputs[2]\dirty!
+ if i\dirty!
prev_i = (curr_i - 1) % #@steps
@display curr_i, true
diff --git a/lib/time.moon b/lib/time.moon
index 007c4de..31fad58 100644
--- a/lib/time.moon
+++ b/lib/time.moon
@@ -1,6 +1,5 @@
-import Registry, Value, Result, Op from require 'core'
-import ValueInput, EventInput from require 'core.base'
-import match from require 'core.pattern'
+import Registry, Value, Result, Op, ValueInput, EventInput, match
+ from require 'core'
import monotime from require 'system'
class clock extends Op