diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-02-19 21:17:46 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-02-19 21:17:53 +0000 |
| commit | e62ef99244bb3aa906810a5cfd53c0dac68b0f37 (patch) | |
| tree | e7379e2add7014e7124f678f4a7a8edc699fe914 /lib | |
| parent | major refactoring: Const, Stream + ResultNode (diff) | |
| download | alive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.tar.gz alive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.zip | |
merge Const and Stream into Value; Dataflow logic
lib not fully ported / functonial
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/debug.moon | 9 | ||||
| -rw-r--r-- | lib/midi/core.moon | 147 | ||||
| -rw-r--r-- | lib/midi/init.moon | 75 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 59 | ||||
| -rw-r--r-- | lib/osc.moon | 29 | ||||
| -rw-r--r-- | lib/time.moon | 139 | ||||
| -rw-r--r-- | lib/util.moon | 114 |
7 files changed, 331 insertions, 241 deletions
diff --git a/lib/debug.moon b/lib/debug.moon index f642c2e..08a7f78 100644 --- a/lib/debug.moon +++ b/lib/debug.moon @@ -3,10 +3,13 @@ import Op from require 'core' class out extends Op @doc: "(out name-str value) - log value to the console" - setup: (@name, @value) => + setup: (params) => + super params + assert @inputs[2], "need a value" + @assert_types 'str', @inputs[2].type - update: (dt) => - L\print "#{@name\unwrap 'str'}", @value\unwrap! + tick: => + L\print @unwrap_inputs! { :out diff --git a/lib/midi/core.moon b/lib/midi/core.moon index 891de0a..7f661ed 100644 --- a/lib/midi/core.moon +++ b/lib/midi/core.moon @@ -1,5 +1,6 @@ import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' import band, bor, lshift, rshift from require 'bit32' +import Op, Registry from require 'core' MIDI = { [0x9]: 'note-on' @@ -15,75 +16,101 @@ MIDI = { rMIDI = {v,k for k,v in pairs MIDI} -class Input - new: (name) => - @input = RtMidiIn RtMidi.Api.UNIX_JACK - +find_port = (Klass, name) -> + with Klass RtMidi.Api.UNIX_JACK id = nil - for port=1,@input\getportcount! - if name == @input\getportname port + for port=1, \getportcount! + if name == \getportname port id = port break - @input\openport id + \openport id + +class MidiPort + new: (@inp, @out) => - @listeners = {} + dirty: => + @updated == Registry.active!.tick tick: => - while true - delta, bytes = @input\getmessage! - break unless delta - - { status, a, b } = bytes - chan = band status, 0xf - status = MIDI[rshift status, 4] - @dispatch status, chan, a, b - - dispatch: (status, chan, a, b) => - L\trace "dispatching MIDI event #{status} CH#{chan} #{a} #{b}" - for mask, handler in pairs @listeners - match = true - match and= status == mask.status if mask.status - match and= chan == mask.chan if mask.chan - match and= a == mask.a if mask.a - if match - handler status, chan, a, b - - -- register a handler - -- mask is { :status, :chan, :a } (all keys optional) - -- returns mask for op convenience - attach: (mask, handler) => - @listeners[mask] = handler - mask - - detach: (mask) => - @listeners[mask] = nil - -class Output - new: (name) => - @output = RtMidiOut RtMidi.Api.UNIX_JACK + if @inp + @messages = while true + delta, bytes = @inp\getmessage! + break unless delta + + { status, a, b } = bytes + chan = band status, 0xf + status = MIDI[rshift status, 4] + { :status, :chan, :a, :b } + + if @messages + @updated = Registry.active!.tick + + receive: => + assert @inp, "#{@} is not an input port" + return unless @messages + coroutine.wrap -> + for msg in *@messages + coroutine.yield msg - id = nil - for port=1,@output\getportcount! - if name == @output\getportname port - id = port - break + send: (status, chan, a, b) => + assert @out, "#{@} is not an output port" + if 'string' == type 'status' + status = bor (lshift rMIDI[status], 4), chan + @out\sendmessage status, a, b - @output\openport id +class input extends Op + @doc: "(midi/input name) - create a MIDI input port" - send: (status, chan, a, b) => - status = bor (lshift rMIDI[status], 4), chan - @output\sendmessage status, a, b + new: => + super 'midi/port' + @impulses = { Registry.active!.kr } + + setup: (params) => + super params + @assert_types 'str' + + tick: (first) => + { name } = @inputs + if first or name\dirty! + @out\set MidiPort find_port RtMidiIn, name\unwrap! + + @out\unwrap!\tick! + +class output extends Op + @doc: "(midi/output name) - create a MIDI output port" + + new: => + super 'midi/port' + + setup: (params) => + super params + @assert_types 'str' + + tick: (first) => + { name } = @inputs + if first or name\dirty! + @out\set MidiPort nil, find_port RtMidiOut, name\unwrap! + +class inout extends Op + @doc: "(midi/inout inname outname) - create a bidirectional MIDI port" + + new: => + super 'midi/port' + @impulses = { Registry.active!.kr } + + setup: (params) => + super params + @assert_types 'str', 'str' + + tick: (first) => + { inp, out } = @inputs -class InOut - new: (inp, out) => - @inp = Input inp - @out = Output out + if first or inp\dirty! or out\dirty! + inp, out = inp\unwrap!, out\unwrap! + @out\set MidiPort (find_port RtMidiIn, inp), (find_port RtMidiOut, out) - tick: (...) => @inp\tick ... - attach: (...) => @inp\attach ... - detach: (...) => @inp\detach ... - send: (...) => @out\send ... + @out\unwrap!\tick! apply_range = (range, val) -> if range.type == 'str' @@ -100,8 +127,8 @@ apply_range = (range, val) -> error "range has to be a string or number" { - :Input - :Output - :InOut + :input + :output + :inout :apply_range } diff --git a/lib/midi/init.moon b/lib/midi/init.moon index c9e2476..03f5115 100644 --- a/lib/midi/init.moon +++ b/lib/midi/init.moon @@ -1,34 +1,31 @@ -import Stream, Const, Op from require 'core' -import Input, apply_range from require 'lib.midi.core' - -dispatch = Input 'system:midi_capture_4' +import Value, Op from require 'core' +import input, output, inout, apply_range from require 'lib.midi.core' class gate extends Op - @doc: "(midi/gate note [chan]) - gate from note-on and note-off messages" + @doc: "(midi/gate port note [chan]) - gate from note-on and note-off messages" - destroy: => - dispatch\detach @mask if @mask - - setup: (note, chan) => - dispatch\detach @mask if @mask + new: => + super 'bool', false - note = note\const!\unwrap 'num' - chan = chan and chan\const!\unwrap 'num' - @value = false + setup: (params) => + super params + @inputs[3] or= Value.num -1 + @assert_types 'midi/port', 'num', 'num' + @impulses = { @inputs[1]\unwrap! } - @mask = dispatch\attach { :chan, a: note }, (status) -> - if status == 'note-on' - @out\set true - else if status == 'note-off' - @out\set false + tick: => + local port, note, chan + { port, note, chan } = [i\unwrap! for i in *@inputs] - @out = Stream 'bool', false - @out - - update: (dt) => dispatch\tick! + 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 cc [chan [range]]) - MIDI CC to number + @doc: "(midi/cc port cc [chan [range]]) - MIDI CC to number range can be one of: - 'raw' [ 0 - 128[ @@ -37,24 +34,36 @@ range can be one of: - 'rad' [ 0 - tau[ - (num) [ 0 - num[" - destroy: => - dispatch\detach @mask if @mask + new: => + super 'num' - setup: (cc, chan, @range=Const.str'uni') => + destroy: => dispatch\detach @mask if @mask - cc = cc\const!\unwrap 'num' - chan = chan and chan\const!\unwrap 'num' + setup: (params) => + super params + @inputs[3] or= Value.num -1 + @inputs[4] or= Value.str 'uni' + @assert_types 'midi/port', 'num', 'num', 'str' + @impulses = { @inputs[1]\unwrap! } - @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) -> - @out\set apply_range @range, val + if not @out\unwrap! + @out\set apply_range @inputs[4], 0 - @out = Stream 'num', 0 - @out + tick: => + local port, cc, chan + { port, cc, chan } = [i\unwrap! for i in *@inputs] - update: (dt) => dispatch\tick! + 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 @inputs[4], msg.b { + :input + :output + :inout :gate :cc } diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon index 778a724..c0d5e24 100644 --- a/lib/midi/launchctl.moon +++ b/lib/midi/launchctl.moon @@ -1,13 +1,12 @@ -import Stream, Const, Op from require 'core' -import InOut, apply_range from require 'lib.midi.core' +import Value, Op from require 'core' +import apply_range from require 'lib.midi.core' import bor, lshift from require 'bit32' -launch = InOut 'system:midi_capture_4', 'system:midi_playback_4' - +unpack or= table.unpack color = (r, g) -> bor 12, r, (lshift g, 4) class cc_seq extends Op - @doc: "(launctl/cc-seq i start chan [steps [range]]) - CC-Sequencer + @doc: "(launctl/cc-seq port i start chan [steps [range]]) - CC-Sequencer returns the value for the i-th step steps (buttons starting from start). steps defaults to 8. @@ -19,43 +18,41 @@ range can be one of: - 'rad' [ 0 - tau[ - (num) [ 0 - num[" - destroy: => - launch\detach @mask if @mask - new: => + super 'num' @steps = {} - @out = Stream 'num' - setup: (@i, start, chan, steps=(Const.num 8), @range=Const.str 'uni') => - launch\detach @mask if @mask + setup: (params) => + super params - @start = start\const!\unwrap 'num' - @chan = chan\const!\unwrap 'num' - steps = steps\const!\unwrap 'num' + @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! } - while steps > #@steps - table.insert @steps, 0 - while steps < #@steps - table.remove @steps - - @mask = launch\attach { status: 'control-change', chan: @chan }, (_, _, cc, val) -> @change cc, val - @out + if not @out\unwrap! + @out\set apply_range @inputs[6], 0 - change: (cc, val) => - i = cc - @start - if i < #@steps - @steps[i+1] = val + tick: => + port, i, start, chan, steps = unpack [i\unwrap! for i in *@inputs] + port = @inputs[1]\unwrap! + _, i, start, chan, steps = unpack @inputs - update: (dt) => - launch\tick! - - curr_i = (@i\unwrap 'num') % #@steps + curr_i = i\unwrap! % #@steps + changed = false - @out\set apply_range @range, @steps[curr_i+1] + for msg in port\receive! + if msg.status == 'control-change' and msg.chan == chan + i = msg.a - start + if i < #@steps + @steps[i+1] = msg.b + changed = i == curr_i + if changed or i\dirty! or start\dirty! or chan\dirty! or steps\diry! + @out\set apply_range @inputs[6], @steps[curr_i+1] class gate_seq extends Op - @doc: "(launctl/gate-seq i start chan [steps]) - Gate-Sequencer + @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." diff --git a/lib/osc.moon b/lib/osc.moon index fed1697..3bac3f1 100644 --- a/lib/osc.moon +++ b/lib/osc.moon @@ -2,22 +2,39 @@ import Stream, Op from require 'core' import pack, unpack from require 'osc' import dns, udp from require 'socket' +class addr extends Op + @doc: "(remote host port) - UDP remote definition" + + new: => + super 'udp/remote' + @@udp or= udp! + + setup: (params) => + super params + @assert_types 'str', 'num' + + tick: => + host, port = @unwrap_inputs! + ip = dns.toip host + @out\set { :ip, :port } + class out extends Op @doc: "(out host port path val) - send a value via OSC" new: (...) => - super ... - @@udp or= udp! - setup: (@host, @port, @path, @value) => + setup: (params) => + super params + assert @inputs[3], "need a value" + @assert_types 'udp/remote', 'str', @inputs[3].type update: (dt) => - ip = dns.toip @host\unwrap 'str' - port = @port\unwrap 'num' - msg = pack (@path\unwrap 'str'), @value\unwrap! + remote, path, value = @unwrap_inputs! + msg = pack path, value @@udp\sendto msg, ip, port { + :addr :out } diff --git a/lib/time.moon b/lib/time.moon index 7ce8b84..c49a7d7 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -1,7 +1,31 @@ -import Stream, Const, Op from require 'core' +import Registry, Value, Op from require 'core' +import monotime from require 'system' + +delta = do + period = 1 / 60 + + local last + -> + +class clock extends Op + new: => + super 'num' + @impulses = { Registry.active!.kr } + @out\set 0 + + setup: (params) => + super params + @last = monotime! + + tick: => + time = monotime! + dt = time - @last + if dt >= 1/60 + @out\set dt + @last = time class lfo extends Op - @doc: "(lfo freq [wave]) - low-frequency oscillator + @doc: "(lfo clock freq [wave]) - low-frequency oscillator oscillates between 0 and 1 at the frequency freq. wave selects the wave shape from the following (default sin): @@ -10,89 +34,96 @@ wave selects the wave shape from the following (default sin): - tri" tau = math.pi * 2 - new: (...) => - super ... - print "creating LFO" - @out = Stream 'num' + new: => + super 'num' @phase = 0 - default_wave = Const 'str', 'sin' - setup: (@freq, @wave=default_wave) => - assert @freq, "lfo requires a frequency value" - L\trace "setup #{@}, freq=#{@freq}, wave=#{@wave}" - @out + default_wave = Value.str 'sin' + setup: (params) => + super params - update: (dt) => - @phase += dt * @freq\unwrap 'num' - @out\set switch @wave\unwrap! - when 'sin' then .5 + .5 * math.cos @phase * tau - when 'saw' then @phase % 1 - when 'tri' then math.abs (2*@phase % 2) - 1 - else error "unknown wave type" + @inputs[3] or= default_wave + @assert_types 'num', 'num', 'str' + + tick: => + -- if clock is dirty + if @inputs[1].dirty + dt, freq, wave = @unwrap_inputs! + @phase += dt * freq + @out\set switch wave + when 'sin' then .5 + .5 * math.cos @phase * tau + when 'saw' then @phase % 1 + when 'tri' then math.abs (2*@phase % 2) - 1 + else error "unknown wave type" class ramp extends Op - @doc: "(ramp period [max]) - sawtooth lfo + @doc: "(ramp clock period [max]) - sawtooth lfo -ramps from 0 to max (default same as ramp) once every period seconds" +ramps from 0 to max (default same as ramp) once every period seconds." - new: (...) => - super ... - @out = Stream 'num' + new: => + super 'num' @phase = 0 - @out - setup: (@period, @max) => - assert @period, "tick requires a period value" + setup: (params) => + super params + assert @inputs[1].type == 'num', "tick requires a clock value" + assert @inputs[2].type == 'num', "tick requires a period value" - update: (dt) => - period = @period\unwrap 'num' - max = if @max then @max\unwrap! else period - @phase += dt / period + tick: => + -- if clock is dirty + if @inputs[1].dirty + dt, period, max = @unwrap_inputs! + max or= period + @phase += dt / period - if @phase >= 1 - @phase -= 1 + if @phase >= 1 + @phase -= 1 - @out\set @phase * max + @out\set @phase * max class tick extends Op - @doc: "(tick period) - count ticks + @doc: "(tick clock period) - count ticks counts upwards by one every period seconds and returns the number of completed ticks." - new: (...) => - super ... - @out = Stream 'num' + new: => + super 'num' @phase = 0 - setup: (@period) => - assert @period, "tick requires a period value" - @out + setup: (params) => + @assert_types 'num', 'num' update: (dt) => - @phase += dt / @period\unwrap 'num' - @out\set math.floor @phase + -- if clock is dirty + if @inputs[1].dirty + dt, period = @unwrap_inputs! + @phase += dt / period + + @out\set math.floor @phase class every extends Op - @doc: "(every period) - sometimes true + @doc: "(every clock period) - trigger every period seconds returns true once every period seconds." - new: (...) => - super ... - @out = Stream 'bool' + new: => + super 'bang' @phase = 0 setup: (@period) => - assert @period, "every requires a period value" - @out + @assert_types 'num', 'num' update: (dt) => - @phase += dt / @period\unwrap 'num' - if @phase > 1 - @phase -= 1 - @out\set true - else - @out\set false + -- if clock is dirty + if @inputs[1].dirty + dt, period = @unwrap_inputs! + @phase += dt / period + + if @phase >= 1 + @phase -= 1 + @out\set true { + :clock :lfo :ramp :tick diff --git a/lib/util.moon b/lib/util.moon index 192c489..04d012f 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -1,4 +1,4 @@ -import Stream, Const, Op from require 'core' +import Op from require 'core' class switch_ extends Op @doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs @@ -7,85 +7,91 @@ 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: (@i, ...) => - @choices = { ... } + setup: (params) => + super params - typ = @choices[1].type - for inp in *@choices[2,] - assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}" + { i, first } = @inputs + assert i.type == 'bool' or i.type == 'num', "#{@}: i has to be bool or num" - @out = Stream typ - @out + for inp in *@inputs[3,] + assert inp.type == first.type, "not all values have the same type: #{first.type} != #{inp.type}" + @out = Stream first.type update: (dt) => - i = @i\unwrap! + i = @inputs[1]\unwrap! active = switch i when true - @choices[1] + @inputs[2] when false - @choices[2] + @inputs[3] else - i = 1 + (math.floor i) % #@choices - @choices[i] + i = 2 + (math.floor i) % (#@inputs - 1) + @inputs[i] @out\set active and active\unwrap! -class switch_pause extends Op - @doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs +--class switch_pause extends Op +-- @doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs +-- +--like (switch ...) except that the unused inputs are paused." +-- +-- setup: (@i, ...) => +-- @choices = { ... } +-- +-- typ = @choices[1].type +-- for inp in *@choices[2,] +-- assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}" +-- +-- @out = Stream typ +-- @out +-- +-- update: (dt) => +-- i = @i\unwrap! +-- active = switch i +-- when true +-- @choices[1] +-- when false +-- @choices[2] +-- else +-- i = 1 + (math.floor i) % #@choices +-- @choices[i] +-- +-- @out\set if active +-- active\unwrap! -like (switch ...) except that the unused inputs are paused." - - setup: (@i, ...) => - @choices = { ... } - - typ = @choices[1].type - for inp in *@choices[2,] - assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}" - - @out = Stream typ - @out - - update: (dt) => - i = @i\unwrap! - active = switch i - when true - @choices[1] - when false - @choices[2] - else - i = 1 + (math.floor i) % #@choices - @choices[i] +class edge extends Op + @doc: "(edge bool) - convert rising edges to bangs" - @out\set if active - active\unwrap! + new: => + super 'bang' -class edge extends Op - setup: (@i) => - @last = false - @out = Stream @i.type - @out + setup: (params) => + super params + @assert_types 'bool' update: (dt) => - now = @i\unwrap! - @out\set not @last and now - @last = now + now = @params[1]\unwrap! + if now and not @last + @out\set true + @last = now class keep extends Op - @doc: "(keep value [default]) - keep the last non-nil value + @doc: "(keep value [init]) - keep the last non-nil value always reproduces the last non-nil value the input produced or default. default defaults to zero." - setup: (@i, @default=Const.num 0) => - @out = Stream @i.type, @default.value - @out + setup: (params) => + super params + { i, init } = @inputs + @out = Value i.type, default and init\unwrap! - update: (dt) => - if next = @i\unwrap! + tick => + if next = @params[1]\unwrap! @out\set next { 'switch': switch_ - 'switch-': switch_pause +-- 'switch-': switch_pause :edge :keep } |
