aboutsummaryrefslogtreecommitdiffstats
path: root/lib/midi
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/midi
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/midi')
-rw-r--r--lib/midi/core.moon147
-rw-r--r--lib/midi/init.moon75
-rw-r--r--lib/midi/launchctl.moon59
3 files changed, 157 insertions, 124 deletions
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."