diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-04-14 09:21:00 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-04-14 09:21:00 +0000 |
| commit | 5faab5d81cc5ed36824a52a273e83eb20240cb2e (patch) | |
| tree | dba10fadf4cdad0f5524df642fab83f6310a8574 /alv-lib/midi | |
| parent | move spec out of spec/core (diff) | |
| download | alive-5faab5d81cc5ed36824a52a273e83eb20240cb2e.tar.gz alive-5faab5d81cc5ed36824a52a273e83eb20240cb2e.zip | |
move lib to alv-lib module
Diffstat (limited to 'alv-lib/midi')
| -rw-r--r-- | alv-lib/midi/core.moon | 122 | ||||
| -rw-r--r-- | alv-lib/midi/launchctl.moon | 194 |
2 files changed, 316 insertions, 0 deletions
diff --git a/alv-lib/midi/core.moon b/alv-lib/midi/core.moon new file mode 100644 index 0000000..ddb1e8a --- /dev/null +++ b/alv-lib/midi/core.moon @@ -0,0 +1,122 @@ +import ValueStream, IOStream, Op, Input, Error, val from require 'alv.base' +import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' + +bit = do + ok, bit = pcall require, 'bit32' + if ok then bit else require 'bit' +import band, bor, lshift, rshift from bit + +MIDI = { + [0x9]: 'note-on' + [0x8]: 'note-off' + + [0xa]: 'after-key' + [0xd]: 'after-channel' + + [0xb]: 'control-change' + [0xe]: 'pitch-bend' + [0xc]: 'program-change' +} + +rMIDI = {v,k for k,v in pairs MIDI} + +find_port = (Klass, name) -> + with Klass RtMidi.Api.UNIX_JACK + id = nil + for port=1, \getportcount! + if name == \getportname port + id = port + break + + \openport id + +class MidiPort extends IOStream + new: => super 'midi/port' + + setup: (inp, out) => + @inp = inp and find_port RtMidiIn, inp + @out = out and find_port RtMidiOut, out + + tick: => + return unless @inp + while true + delta, bytes = @inp\getmessage! + break unless delta + + { 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" + if 'string' == type 'status' + status = bor (lshift rMIDI[status], 4), chan + @out\sendmessage status, a, b + +class PortOp extends Op + new: (...) => + super ... + @out or= MidiPort! + + tick: (inp, out) => + { :inp, :out } = @unwrap_all! + @out\setup inp, out + +input = ValueStream.meta + meta: + name: 'input' + summary: "Create a MIDI input port." + examples: { '(midi/input name)' } + + value: class extends PortOp + setup: (inputs) => + name = val.str\match inputs + super inp: Input.hot name + +output = ValueStream.meta + meta: + name: 'output' + summary: "Create a MIDI output port." + examples: { '(midi/output name)' } + + value: class extends PortOp + setup: (inputs) => + name = val.str\match inputs + super out: Input.hot name + +inout = ValueStream.meta + meta: + name: 'inout' + summary: "Create a bidirectional MIDI port." + examples: { '(midi/inout name)' } + + value: class extends PortOp + setup: (inputs) => + { inp, out } = (val.str + val.str)\match inputs + super + inp: Input.hot inp + out: Input.hot out + +apply_range = (range, val) -> + if range\type! == 'str' + switch range! + when 'raw' then val + when 'uni' then val / 128 + when 'bip' then val / 64 - 1 + when 'rad' then val / 64 * math.pi + when 'deg' then val / 128 * 360 + else + error Error 'argument', "unknown range '#{range!}'" + elseif range.type == 'num' + val / 128 * range! + else + error Error 'argument', "range has to be a string or number" + +{ + :input + :output + :inout + :apply_range + :bit +} diff --git a/alv-lib/midi/launchctl.moon b/alv-lib/midi/launchctl.moon new file mode 100644 index 0000000..90e8956 --- /dev/null +++ b/alv-lib/midi/launchctl.moon @@ -0,0 +1,194 @@ +import ValueStream, EventStream, Op, Input, val, 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 = ValueStream.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 = 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.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' + + @state or= {} + @out or= ValueStream 'num', 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 port\dirty! + changed = false + 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 + @state[rel_i+1] = msg.b + changed = rel_i == curr_i + @out\set apply_range range, @state[curr_i+1] if changed + else + @out\set apply_range range, @state[curr_i+1] + +gate_seq = ValueStream.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'] + 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.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 + + @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 + +{ + 'cc-seq': cc_seq + 'gate-seq': gate_seq + 'trig-seq': trig_seq +} |
