aboutsummaryrefslogtreecommitdiffstats
path: root/lib/midi
diff options
context:
space:
mode:
Diffstat (limited to 'lib/midi')
-rw-r--r--lib/midi/core.moon70
-rw-r--r--lib/midi/launchctl.moon133
2 files changed, 128 insertions, 75 deletions
diff --git a/lib/midi/core.moon b/lib/midi/core.moon
index d1930eb..e520bbf 100644
--- a/lib/midi/core.moon
+++ b/lib/midi/core.moon
@@ -1,4 +1,4 @@
-import Value, IO, Op, Registry, Input, Error, match from require 'core.base'
+import ValueStream, IOStream, Op, Input, Error, val from require 'core.base'
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
bit = do
@@ -30,27 +30,23 @@ find_port = (Klass, name) ->
\openport id
-class MidiPort extends IO
- new: (@inp, @out) =>
- @messages = {}
-
- tick: =>
- if @inp
- @messages = while true
- delta, bytes = @inp\getmessage!
- break unless delta
+class MidiPort extends IOStream
+ new: => super 'midi/port'
- { status, a, b } = bytes
- chan = band status, 0xf
- status = MIDI[rshift status, 4]
- { :status, :chan, :a, :b, port: @ }
+ setup: (inp, out) =>
+ @inp = inp and find_port RtMidiIn, inp
+ @out = out and find_port RtMidiOut, out
- dirty: => #@messages > 0
+ tick: =>
+ return unless @inp
+ while true
+ delta, bytes = @inp\getmessage!
+ break unless delta
- receive: =>
- coroutine.wrap ->
- for msg in *@messages
- coroutine.yield msg
+ { status, a, b } = bytes
+ chan = band status, 0xf
+ status = MIDI[rshift status, 4]
+ @add { :status, :chan, :a, :b }
send: (status, chan, a, b) =>
assert @out, Error 'type', "#{@} is not an output or bidirectional port"
@@ -59,15 +55,15 @@ class MidiPort extends IO
@out\sendmessage status, a, b
class PortOp extends Op
- new: => super 'midi/port'
+ new: (...) =>
+ super ...
+ @out or= MidiPort!
tick: (inp, out) =>
- if (inp and inp\dirty!) or (out and out\dirty!)
- inp = inp and find_port RtMidiIn, inp!
- out = out and find_port RtMidiOut, out!
- @out\set MidiPort inp, out
+ { :inp, :out } = @unwrap_all!
+ @out\setup inp, out
-input = Value.meta
+input = ValueStream.meta
meta:
name: 'input'
summary: "Create a MIDI input port."
@@ -75,12 +71,10 @@ input = Value.meta
value: class extends PortOp
setup: (inputs) =>
- { name } = match 'str', inputs
- super name: Input.value name
-
- tick: => super @inputs.name
+ name = val.str\match inputs
+ super inp: Input.hot name
-output = Value.meta
+output = ValueStream.meta
meta:
name: 'output'
summary: "Create a MIDI output port."
@@ -88,12 +82,10 @@ output = Value.meta
value: class extends PortOp
setup: (inputs) =>
- { name } = match 'str', inputs
- super name: Input.value name
+ name = val.str\match inputs
+ super out: Input.hot name
- tick: => super nil, @inputs.name
-
-inout = Value.meta
+inout = ValueStream.meta
meta:
name: 'inout'
summary: "Create a bidirectional MIDI port."
@@ -101,12 +93,10 @@ inout = Value.meta
value: class extends PortOp
setup: (inputs) =>
- { inp, out } = match 'str str', inputs
+ { inp, out } = (val.str + val.str)\match inputs
super
- inp: Input.value inp
- out: Input.value out
-
- tick: => super @inputs.inp, @inputs.out
+ inp: Input.hot inp
+ out: Input.hot out
apply_range = (range, val) ->
if range\type! == 'str'
diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon
index 37ccb82..1e33cc4 100644
--- a/lib/midi/launchctl.moon
+++ b/lib/midi/launchctl.moon
@@ -1,15 +1,15 @@
-import Value, Op, Input, match from require 'core.base'
+import ValueStream, EventStream, Op, Input, val, evt from require 'core.base'
import apply_range, bit from require 'lib.midi.core'
import bor, lshift from bit
unpack or= table.unpack
color = (r, g) -> bit.bor 12, r, (bit.lshift g, 4)
-cc_seq = Value.meta
+cc_seq = ValueStream.meta
meta:
name: 'cc-seq'
summary: "MIDI CC-Sequencer."
- examples: { '(launchctl/cc-seq port i start chan [steps [range]])' }
+ 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.
@@ -23,22 +23,21 @@ range can be one of:
- (num) [ 0 - num["
value: class extends Op
- new: => super 'num'
-
- setup: (inputs) =>
- { port, i, start,
- chan, steps, range } = match 'midi/port num num num num? any?', inputs
+ num = val.num
+ pattern = -evt['midi/port'] + num + num + num + -num + -(val.str + num)
+ setup: (inputs, scope) =>
+ { port, i, start, chan, steps, range } = pattern\match inputs
super
- port: Input.io port
- i: Input.value i
- start: Input.value start
- chan: Input.value chan
- steps: Input.value steps or Value.num 8
- range: Input.value range or Value.str 'uni'
+ 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 ValueStream.num 8
+ range: Input.hot range or ValueStream.str 'uni'
- if not @out\unwrap!
- @out\set apply_range @inputs.range, 0
+ @state or= {}
+ @out or= ValueStream 'num', apply_range @inputs.range, 0
tick: =>
{ :port, :i, :start, :chan, :steps, :range } = @inputs
@@ -52,7 +51,7 @@ range can be one of:
curr_i = i! % #@state
if port\dirty!
changed = false
- for msg in port!\receive!
+ for msg in *port!
if msg.status == 'control-change' and msg.chan == chan!
rel_i = msg.a - start!
if rel_i >= 0 and rel_i < #@state
@@ -62,29 +61,28 @@ range can be one of:
else
@out\set apply_range range, @state[curr_i+1]
-gate_seq = Value.meta
+gate_seq = ValueStream.meta
meta:
name: 'gate-seq'
summary: "MIDI Gate-Sequencer."
- examples: { '(launchctl/gate-seq port i start chan [steps])' }
+ examples: { '(launchctl/gate-seq [port] i start chan [steps])' }
description: "
-returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from
+Send `true` or `false` for the `i`-th note-button (MIDI-notes starting from
`start`). `steps` defaults to 8."
value: class extends Op
- new: =>
- super 'bool', false
- @state = {}
-
- setup: (inputs) =>
- { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs
+ pattern = -evt['midi/port'] + val.num + val.num + val.num + -val.num
+ setup: (inputs, scope) =>
+ @out or= ValueStream 'bool'
+ @state or= {}
+ { port, i, start, chan, steps } = pattern\match inputs
super
- port: Input.io port
- i: Input.value i
- start: Input.value start
- chan: Input.value chan
- steps: Input.value steps or Value.num 8
+ 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 ValueStream.num 8
light = (set, active) ->
set = if set then 'S' else ' '
@@ -96,8 +94,8 @@ returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from
when 'SA' then 3, 1
display: (i, active) =>
- { :port, :start, :chan } = @unwrap_all!
- port\send 'note-on', chan, (start + i), light @state[i+1], 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
@@ -111,7 +109,7 @@ returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from
curr_i = i! % #@state
if port\dirty!
- for msg in port!\receive!
+ for msg in *port!
if msg.status == 'note-on' and msg.chan == chan!
rel_i = msg.a - start!
if rel_i >= 0 and rel_i < #@state
@@ -126,7 +124,72 @@ returns `true` or `false` for the `i`-th note-button (MIDI-notes starting from
@out\set @state[curr_i+1]
+trig_seq = ValueStream.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'] + val.num + val.num + val.num + -val.num
+ setup: (inputs, scope) =>
+ @out or= EventStream 'bang'
+ @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 ValueStream.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 port\dirty!
+ for msg in *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\add true
+
{
- 'gate-seq': gate_seq
'cc-seq': cc_seq
+ 'gate-seq': gate_seq
+ 'trig-seq': trig_seq
}