diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-09-05 11:06:48 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-03-02 14:24:49 +0000 |
| commit | 2851bc1f5ea4c5d869706abca2af87271ea8d52a (patch) | |
| tree | 3247f903f29625c6392a499094b66eaa32d1cd00 | |
| parent | add math/inc and math/dec ops (diff) | |
| download | alive-2851bc1f5ea4c5d869706abca2af87271ea8d52a.tar.gz alive-2851bc1f5ea4c5d869706abca2af87271ea8d52a.zip | |
update alv/midi
| -rw-r--r-- | alv-lib/midi.moon | 94 | ||||
| -rw-r--r-- | alv-lib/midi/core.moon | 82 | ||||
| -rw-r--r-- | alv-lib/midi/launchctl.moon | 195 |
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 |
