aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-02-19 21:17:46 +0000
committers-ol <s-ol@users.noreply.github.com>2020-02-19 21:17:53 +0000
commite62ef99244bb3aa906810a5cfd53c0dac68b0f37 (patch)
treee7379e2add7014e7124f678f4a7a8edc699fe914 /lib
parentmajor refactoring: Const, Stream + ResultNode (diff)
downloadalive-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.moon9
-rw-r--r--lib/midi/core.moon147
-rw-r--r--lib/midi/init.moon75
-rw-r--r--lib/midi/launchctl.moon59
-rw-r--r--lib/osc.moon29
-rw-r--r--lib/time.moon139
-rw-r--r--lib/util.moon114
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
}