aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-09-05 11:06:48 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-02 14:24:49 +0000
commit2851bc1f5ea4c5d869706abca2af87271ea8d52a (patch)
tree3247f903f29625c6392a499094b66eaa32d1cd00
parentadd math/inc and math/dec ops (diff)
downloadalive-2851bc1f5ea4c5d869706abca2af87271ea8d52a.tar.gz
alive-2851bc1f5ea4c5d869706abca2af87271ea8d52a.zip
update alv/midi
-rw-r--r--alv-lib/midi.moon94
-rw-r--r--alv-lib/midi/core.moon82
-rw-r--r--alv-lib/midi/launchctl.moon195
3 files changed, 119 insertions, 252 deletions
diff --git a/alv-lib/midi.moon b/alv-lib/midi.moon
index 70e3985..a42867e 100644
--- a/alv-lib/midi.moon
+++ b/alv-lib/midi.moon
@@ -1,5 +1,5 @@
import Constant, Op, Input, T, sig, evt from require 'alv.base'
-import input, output, inout, apply_range from require 'alv-lib.midi.core'
+import input, output, port, apply_range from require 'alv-lib.midi.core'
gate = Constant.meta
meta:
@@ -8,27 +8,40 @@ gate = Constant.meta
examples: { '(midi/gate [port] note [chan])' }
value: class extends Op
- pattern = -evt['midi/port'] + sig.num -sig.num
+ pattern = -sig['midi/in'] + sig.num -sig.num
setup: (inputs, scope) =>
- @out or= T.bool\mk_sig!
{ port, note, chan } = pattern\match inputs
+ @out or= T.bool\mk_sig!
super
- port: Input.hot port or scope\get '*midi*'
+ port: Input.cold port or scope\get '*midi*'
note: Input.hot note
chan: Input.hot chan or Constant.num -1
- tick: =>
- { :port, :note, :chan } = @inputs
+ internal: Input.hot T.bool\mk_sig!
- if note\dirty! or chan\dirty!
- @out\set false
+ poll: =>
+ { :port, :note, :chan, :internal } = @inputs
- if msg = port!
+ msgs = port!.msgs
+ for i = #msgs, 1, -1
+ msg = msgs[i]
if msg.a == note! and (chan! == -1 or msg.chan == chan!)
if msg.status == 'note-on'
- @out\set true
+ internal.result\set true
+ return true
elseif msg.status == 'note-off'
- @out\set false
+ internal.result\set false
+ return true
+
+ false
+
+ tick: =>
+ { :note, :chan, :internal } = @inputs
+
+ if note\dirty! or chan\dirty!
+ @out\set false
+ elseif internal\dirty!
+ @out\set internal!
trig = Constant.meta
meta:
@@ -37,22 +50,32 @@ trig = Constant.meta
examples: { '(midi/trig [port] note [chan])' }
value: class extends Op
- pattern = -evt['midi/port'] + sig.num -sig.num
+ pattern = -sig['midi/in'] + sig.num -sig.num
setup: (inputs, scope) =>
- @out or= T.bang\mk_evt!
{ port, note, chan } = pattern\match inputs
+ @out or= T.bang\mk_evt!
super
- port: Input.hot port or scope\get '*midi*'
+ port: Input.cold port or scope\get '*midi*'
note: Input.cold note
chan: Input.cold chan or Constant.num -1
- tick: =>
- { :port, :note, :chan } = @inputs
+ internal: Input.hot T.bang\mk_evt!
+
+ poll: =>
+ { :port, :note, :chan, :internal } = @inputs
- if msg = port!
+ msgs = port!.msgs
+ for i = #msgs, 1, -1
+ msg = msgs[i]
if msg.a == note! and (chan! == -1 or msg.chan == chan!)
if msg.status == 'note-on'
- @out\set true
+ internal.result\set true
+ return true
+
+ false
+
+ tick: =>
+ @out\set @inputs.internal!
cc = Constant.meta
meta:
@@ -70,25 +93,38 @@ cc = Constant.meta
- (num) [ 0 - num["
value: class extends Op
- pattern = -evt['midi/port'] + sig.num + -sig.num + -sig.num
+ pattern = -sig['midi/in'] + sig.num + -sig.num + -sig.num
setup: (inputs, scope) =>
{ port, cc, chan, range } = pattern\match inputs
super
- port: Input.hot port or scope\get '*midi*'
+ port: Input.cold port or scope\get '*midi*'
cc: Input.cold cc
chan: Input.cold chan or Constant.num -1
range: Input.cold range or Constant.str 'uni'
- @out or= T.num\mk_sig apply_range @inputs.range, 0
+ internal: Input.hot T.num\mk_sig 0
+
+ @out or= T.num\mk_sig!
+
+ poll: =>
+ { :port, :cc, :chan, :internal } = @inputs
+
+ msgs = port!.msgs
+ for i = #msgs, 1, -1
+ msg = msgs[i]
+ if msg.a == cc! and (chan! == -1 or msg.chan == chan!)
+ if msg.status == 'control-change'
+ internal.result\set msg.b
+ return true
+
+ false
tick: =>
- { :port, :cc, :chan, :range } = @inputs
- if msg = port!
- if msg.status == 'control-change' and
- (chan! == -1 or msg.chan == chan!) and
- msg.a == cc!
- @state = msg.b / 128
- @out\set apply_range range, msg.b
+ { :range, :internal } = @inputs
+
+ value = internal!
+ @state = value / 128
+ @out\set apply_range range, value
vis: =>
{
@@ -104,7 +140,7 @@ Constant.meta
value:
:input
:output
- :inout
+ :port
:gate
:trig
:cc
diff --git a/alv-lib/midi/core.moon b/alv-lib/midi/core.moon
index 15ae3d6..03cfcb2 100644
--- a/alv-lib/midi/core.moon
+++ b/alv-lib/midi/core.moon
@@ -1,7 +1,14 @@
-import Constant, IOStream, Op, Input, T, Error, sig from require 'alv.base'
+import Constant, T, Struct, Op, Input, T, Error, const from require 'alv.base'
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
-bit = do
+bit = if _VERSION == 'Lua 5.4'
+ {
+ band: (a, b) -> a & b
+ bor: (a, b) -> a | b
+ lshift: (a, b) -> a << b
+ rshift: (a, b) -> a >> b
+ }
+else
ok, bit = pcall require, 'bit32'
if ok then bit else require 'bit'
import band, bor, lshift, rshift from bit
@@ -30,38 +37,49 @@ find_port = (Klass, name) ->
\openport id
-class MidiPort extends IOStream
- new: => super T['midi/port']
-
- setup: (inp, out) =>
- @inp = inp and find_port RtMidiIn, inp
- @out = out and find_port RtMidiOut, out
+class InPort
+ new: (@name) =>
+ @port = find_port RtMidiIn, @name
+ @msgs = {}
poll: =>
- return unless @inp
- while true
- delta, bytes = @inp\getmessage!
+ @msgs = while true
+ delta, bytes = @port\getmessage!
break unless delta
-
{ status, a, b } = bytes
chan = band status, 0xf
status = MIDI[rshift status, 4]
- @add { :status, :chan, :a, :b }
+ { :status, :chan, :a, :b }
+
+ __tostring: => "[#{@name}]"
+ __tojson: => string.format '%q', tostring @
+
+class OutPort
+ new: (@name) =>
+ @port = find_port RtMidiOut, @name
send: (status, chan, a, b) =>
- assert @out, Error 'type', "#{@} is not an output or bidirectional port"
if 'string' == type 'status'
status = bor (lshift rMIDI[status], 4), chan
- @out\sendmessage status, a, b
+ @port\sendmessage status, a, b
-class PortOp extends Op
- new: (...) =>
- super ...
- @out or= MidiPort!
+ __tostring: => "[#{@name}]"
+ __tojson: => string.format '%q', tostring @
- tick: (inp, out) =>
+class PortOp extends Op
+ setup: (inputs) =>
+ super inputs
{ :inp, :out } = @unwrap_all!
- @out\setup inp, out
+
+ if inp and out
+ type = Struct in: T['midi/in'], out: T['midi/out']
+ @out = type\mk_const { 'in': InPort(inp), out: OutPort(out) }
+ elseif inp
+ @out = T['midi/in']\mk_const InPort inp
+ elseif out
+ @out = T['midi/out']\mk_const OutPort out
+ else
+ error "no port opened"
input = Constant.meta
meta:
@@ -71,9 +89,13 @@ input = Constant.meta
value: class extends PortOp
setup: (inputs) =>
- name = sig.str\match inputs
+ name = const.str\match inputs
super inp: Input.hot name
+ poll: =>
+ @.out!\poll!
+ false
+
output = Constant.meta
meta:
name: 'output'
@@ -82,22 +104,26 @@ output = Constant.meta
value: class extends PortOp
setup: (inputs) =>
- name = sig.str\match inputs
+ name = const.str\match inputs
super out: Input.hot name
-inout = Constant.meta
+port = Constant.meta
meta:
- name: 'inout'
+ name: 'port'
summary: "Create a bidirectional MIDI port."
- examples: { '(midi/inout name)' }
+ examples: { '(midi/port name)' }
value: class extends PortOp
setup: (inputs) =>
- { inp, out } = (sig.str + sig.str)\match inputs
+ { inp, out } = (const.str + const.str)\match inputs
super
inp: Input.hot inp
out: Input.hot out
+ poll: =>
+ @.out!.in\poll!
+ false
+
apply_range = (range, val) ->
if range\type! == T.str
switch range!
@@ -116,7 +142,7 @@ apply_range = (range, val) ->
{
:input
:output
- :inout
+ :port
:apply_range
:bit
}
diff --git a/alv-lib/midi/launchctl.moon b/alv-lib/midi/launchctl.moon
deleted file mode 100644
index 8a8d599..0000000
--- a/alv-lib/midi/launchctl.moon
+++ /dev/null
@@ -1,195 +0,0 @@
-import Constant, Op, Input, T, sig, evt from require 'alv.base'
-import apply_range, bit from require 'alv-lib.midi.core'
-import bor, lshift from bit
-
-color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4)
-
-cc_seq = Constant.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.
-
-range can be one of:
-
-- 'raw' [ 0 - 128[
-- 'uni' [ 0 - 1[ (default)
-- 'bip' [-1 - 1[
-- 'rad' [ 0 - tau[
-- 'deg' [ 0 - 360[
-- (num) [ 0 - num["
-
- value: class extends Op
- num = sig.num
- pattern = -evt['midi/port'] + num + num + num + -num + -(sig.str + num)
- setup: (inputs, scope) =>
- { port, i, start, chan, steps, range } = pattern\match inputs
-
- super
- port: Input.hot port or scope\get '*ctrl*'
- i: Input.hot i
- start: Input.hot start
- chan: Input.hot chan
- steps: Input.hot steps or Constant.num 8
- range: Input.hot range or Constant.str 'uni'
-
- @state or= {}
- @out or= T.num\mk_sig 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 msg = port!
- 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
- if rel_i == curr_i
- @out\set apply_range range, @state[curr_i+1]
- else
- @out\set apply_range range, @state[curr_i+1]
-
-gate_seq = Constant.meta
- meta:
- name: 'gate-seq'
- summary: "MIDI Gate-Sequencer."
- examples: { '(launchctl/gate-seq [port] i start chan [steps])' }
- description: "
-Send `true` or `false` for the `i`-th note-button (MIDI-notes starting from
-`start`). `steps` defaults to 8."
-
- value: class extends Op
- pattern = -evt['midi/port'] + sig.num + sig.num + sig.num + -sig.num
- setup: (inputs, scope) =>
- @out or= T.bool\mk_sig!
- @state or= {}
- { port, i, start, chan, steps } = pattern\match inputs
-
- super
- port: Input.hot port or scope\get '*ctrl*'
- i: Input.hot i
- start: Input.hot start
- chan: Input.hot chan
- steps: Input.hot steps or Constant.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) =>
- start, chan = @inputs.start!, @inputs.chan!
- @inputs.port.stream\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 msg = port!
- 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]
-
-trig_seq = Constant.meta
- meta:
- name: 'trig-seq'
- summary: "MIDI Trigger-Sequencer."
- examples: { '(launchctl/trig-seq [port] i start chan [steps])' }
- description: "
-Send bangs for the `i`-th note-button (MIDI-notes starting from `start`).
-`steps` defaults to 8."
-
- value: class extends Op
- pattern = -evt['midi/port'] + sig.num + sig.num + sig.num + -sig.num
- setup: (inputs, scope) =>
- @out or= T.bang\mk_evt!
- @state or= {}
- { port, i, start, chan, steps } = pattern\match inputs
-
- super
- port: Input.hot port or scope\get '*ctrl*'
- i: Input.hot i
- start: Input.hot start
- chan: Input.hot chan
- steps: Input.hot steps or Constant.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) =>
- start, chan = @inputs.start!, @inputs.chan!
- @inputs.port.stream\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 msg = port!
- 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
-
- if @state[curr_i+1]
- @out\set true
-
-Constant.meta
- meta:
- name: 'midi/launchctl'
- summary: "MIDI sequencing with the LaunchControl MIDI controller"
-
- value:
- 'cc-seq': cc_seq
- 'gate-seq': gate_seq
- 'trig-seq': trig_seq