diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 11:41:33 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 11:41:33 +0000 |
| commit | 03853b35a473161f377fb74a05723cacc5cbf36b (patch) | |
| tree | 2e3df044cc0ff3ed24a07d6b5ee31682f077e5fa | |
| parent | new op interface part one (diff) | |
| download | alive-03853b35a473161f377fb74a05723cacc5cbf36b.tar.gz alive-03853b35a473161f377fb74a05723cacc5cbf36b.zip | |
IO system
| -rw-r--r-- | core/base.moon | 25 | ||||
| -rw-r--r-- | core/init.moon | 8 | ||||
| -rw-r--r-- | core/pattern.moon | 11 | ||||
| -rw-r--r-- | core/registry.moon | 8 | ||||
| -rw-r--r-- | core/value.moon | 6 | ||||
| -rw-r--r-- | lib/debug.moon | 4 | ||||
| -rw-r--r-- | lib/midi/core.moon | 77 | ||||
| -rw-r--r-- | lib/midi/init.moon | 39 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 102 | ||||
| -rw-r--r-- | lib/time.moon | 5 |
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 |
