lib: move midi.core to _midi
s-ol
1 year, 3 months ago
0 | 0 | MODULES:=$(sort $(wildcard alv-lib/*.moon)) |
1 | MODULES:=$(filter-out $(wildcard alv-lib/_*.moon), $(MODULES)) | |
1 | 2 | MODULES:=$(MODULES:alv-lib/%.moon=docs/reference/module/%.html) |
2 | 3 | REFERENCE=docs/reference/index.md $(sort $(wildcard docs/reference/[01]*.md)) docs/reference/builtins.html $(MODULES) |
3 | 4 | REFTOC=$(REFERENCE:%.md=%.html) |
0 | import Constant, T, Struct, Op, Input, T, Error, const from require 'alv.base' | |
1 | import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' | |
2 | ||
3 | bit = if _VERSION == 'Lua 5.4' | |
4 | { | |
5 | band: (a, b) -> a & b | |
6 | bor: (a, b) -> a | b | |
7 | lshift: (a, b) -> a << b | |
8 | rshift: (a, b) -> a >> b | |
9 | } | |
10 | else | |
11 | ok, bit = pcall require, 'bit32' | |
12 | if ok then bit else require 'bit' | |
13 | import band, bor, lshift, rshift from bit | |
14 | ||
15 | MIDI = { | |
16 | [0x9]: 'note-on' | |
17 | [0x8]: 'note-off' | |
18 | ||
19 | [0xa]: 'after-key' | |
20 | [0xd]: 'after-channel' | |
21 | ||
22 | [0xb]: 'control-change' | |
23 | [0xe]: 'pitch-bend' | |
24 | [0xc]: 'program-change' | |
25 | } | |
26 | ||
27 | rMIDI = {v,k for k,v in pairs MIDI} | |
28 | ||
29 | find_port = (Klass, name) -> | |
30 | with Klass RtMidi.Api.UNIX_JACK | |
31 | id = nil | |
32 | for port=1, \getportcount! | |
33 | if name == \getportname port | |
34 | id = port | |
35 | break | |
36 | ||
37 | \openport id | |
38 | ||
39 | class InPort | |
40 | new: (@name) => | |
41 | @port = find_port RtMidiIn, @name | |
42 | @msgs = {} | |
43 | ||
44 | poll: => | |
45 | @msgs = while true | |
46 | delta, bytes = @port\getmessage! | |
47 | break unless delta | |
48 | { status, a, b } = bytes | |
49 | chan = band status, 0xf | |
50 | status = MIDI[rshift status, 4] | |
51 | { :status, :chan, :a, :b } | |
52 | ||
53 | __tostring: => "[#{@name}]" | |
54 | __tojson: => string.format '%q', tostring @ | |
55 | ||
56 | class OutPort | |
57 | new: (@name) => | |
58 | @port = find_port RtMidiOut, @name | |
59 | ||
60 | send: (status, chan, a, b) => | |
61 | if 'string' == type 'status' | |
62 | status = bor (lshift rMIDI[status], 4), chan | |
63 | @port\sendmessage status, a, b | |
64 | ||
65 | __tostring: => "[#{@name}]" | |
66 | __tojson: => string.format '%q', tostring @ | |
67 | ||
68 | class PortOp extends Op | |
69 | setup: (inputs) => | |
70 | super inputs | |
71 | { :inp, :out } = @unwrap_all! | |
72 | ||
73 | if inp and out | |
74 | type = Struct in: T['midi/in'], out: T['midi/out'] | |
75 | @out = type\mk_const { 'in': InPort(inp), out: OutPort(out) } | |
76 | elseif inp | |
77 | @out = T['midi/in']\mk_const InPort inp | |
78 | elseif out | |
79 | @out = T['midi/out']\mk_const OutPort out | |
80 | else | |
81 | error "no port opened" | |
82 | ||
83 | input = Constant.meta | |
84 | meta: | |
85 | name: 'input' | |
86 | summary: "Create a MIDI input port." | |
87 | examples: { '(midi/input name)' } | |
88 | ||
89 | value: class extends PortOp | |
90 | setup: (inputs) => | |
91 | name = const.str\match inputs | |
92 | super inp: Input.hot name | |
93 | ||
94 | poll: => | |
95 | @.out!\poll! | |
96 | false | |
97 | ||
98 | output = Constant.meta | |
99 | meta: | |
100 | name: 'output' | |
101 | summary: "Create a MIDI output port." | |
102 | examples: { '(midi/output name)' } | |
103 | ||
104 | value: class extends PortOp | |
105 | setup: (inputs) => | |
106 | name = const.str\match inputs | |
107 | super out: Input.hot name | |
108 | ||
109 | port = Constant.meta | |
110 | meta: | |
111 | name: 'port' | |
112 | summary: "Create a bidirectional MIDI port." | |
113 | examples: { '(midi/port name)' } | |
114 | ||
115 | value: class extends PortOp | |
116 | setup: (inputs) => | |
117 | { inp, out } = (const.str + const.str)\match inputs | |
118 | super | |
119 | inp: Input.hot inp | |
120 | out: Input.hot out | |
121 | ||
122 | poll: => | |
123 | @.out!.in\poll! | |
124 | false | |
125 | ||
126 | apply_range = (range, val) -> | |
127 | if range\type! == T.str | |
128 | switch range! | |
129 | when 'raw' then val | |
130 | when 'uni' then val / 128 | |
131 | when 'bip' then val / 64 - 1 | |
132 | when 'rad' then val / 64 * math.pi | |
133 | when 'deg' then val / 128 * 360 | |
134 | else | |
135 | error Error 'argument', "unknown range '#{range!}'" | |
136 | elseif range\type! == T.num | |
137 | val / 128 * range! | |
138 | else | |
139 | error Error 'argument', "range has to be a string or number" | |
140 | ||
141 | { | |
142 | :input | |
143 | :output | |
144 | :port | |
145 | :apply_range | |
146 | :bit | |
147 | } |
0 | import Constant, T, Struct, Op, Input, T, Error, const from require 'alv.base' | |
1 | import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' | |
2 | ||
3 | bit = if _VERSION == 'Lua 5.4' | |
4 | { | |
5 | band: (a, b) -> a & b | |
6 | bor: (a, b) -> a | b | |
7 | lshift: (a, b) -> a << b | |
8 | rshift: (a, b) -> a >> b | |
9 | } | |
10 | else | |
11 | ok, bit = pcall require, 'bit32' | |
12 | if ok then bit else require 'bit' | |
13 | import band, bor, lshift, rshift from bit | |
14 | ||
15 | MIDI = { | |
16 | [0x9]: 'note-on' | |
17 | [0x8]: 'note-off' | |
18 | ||
19 | [0xa]: 'after-key' | |
20 | [0xd]: 'after-channel' | |
21 | ||
22 | [0xb]: 'control-change' | |
23 | [0xe]: 'pitch-bend' | |
24 | [0xc]: 'program-change' | |
25 | } | |
26 | ||
27 | rMIDI = {v,k for k,v in pairs MIDI} | |
28 | ||
29 | find_port = (Klass, name) -> | |
30 | with Klass RtMidi.Api.UNIX_JACK | |
31 | id = nil | |
32 | for port=1, \getportcount! | |
33 | if name == \getportname port | |
34 | id = port | |
35 | break | |
36 | ||
37 | \openport id | |
38 | ||
39 | class InPort | |
40 | new: (@name) => | |
41 | @port = find_port RtMidiIn, @name | |
42 | @msgs = {} | |
43 | ||
44 | poll: => | |
45 | @msgs = while true | |
46 | delta, bytes = @port\getmessage! | |
47 | break unless delta | |
48 | { status, a, b } = bytes | |
49 | chan = band status, 0xf | |
50 | status = MIDI[rshift status, 4] | |
51 | { :status, :chan, :a, :b } | |
52 | ||
53 | __tostring: => "[#{@name}]" | |
54 | __tojson: => string.format '%q', tostring @ | |
55 | ||
56 | class OutPort | |
57 | new: (@name) => | |
58 | @port = find_port RtMidiOut, @name | |
59 | ||
60 | send: (status, chan, a, b) => | |
61 | if 'string' == type 'status' | |
62 | status = bor (lshift rMIDI[status], 4), chan | |
63 | @port\sendmessage status, a, b | |
64 | ||
65 | __tostring: => "[#{@name}]" | |
66 | __tojson: => string.format '%q', tostring @ | |
67 | ||
68 | class PortOp extends Op | |
69 | setup: (inputs) => | |
70 | super inputs | |
71 | { :inp, :out } = @unwrap_all! | |
72 | ||
73 | if inp and out | |
74 | type = Struct in: T['midi/in'], out: T['midi/out'] | |
75 | @out = type\mk_const { 'in': InPort(inp), out: OutPort(out) } | |
76 | elseif inp | |
77 | @out = T['midi/in']\mk_const InPort inp | |
78 | elseif out | |
79 | @out = T['midi/out']\mk_const OutPort out | |
80 | else | |
81 | error "no port opened" | |
82 | ||
83 | input = Constant.meta | |
84 | meta: | |
85 | name: 'input' | |
86 | summary: "Create a MIDI input port." | |
87 | examples: { '(midi/input name)' } | |
88 | ||
89 | value: class extends PortOp | |
90 | setup: (inputs) => | |
91 | name = const.str\match inputs | |
92 | super inp: Input.hot name | |
93 | ||
94 | poll: => | |
95 | @.out!\poll! | |
96 | false | |
97 | ||
98 | output = Constant.meta | |
99 | meta: | |
100 | name: 'output' | |
101 | summary: "Create a MIDI output port." | |
102 | examples: { '(midi/output name)' } | |
103 | ||
104 | value: class extends PortOp | |
105 | setup: (inputs) => | |
106 | name = const.str\match inputs | |
107 | super out: Input.hot name | |
108 | ||
109 | port = Constant.meta | |
110 | meta: | |
111 | name: 'port' | |
112 | summary: "Create a bidirectional MIDI port." | |
113 | examples: { '(midi/port name)' } | |
114 | ||
115 | value: class extends PortOp | |
116 | setup: (inputs) => | |
117 | { inp, out } = (const.str + const.str)\match inputs | |
118 | super | |
119 | inp: Input.hot inp | |
120 | out: Input.hot out | |
121 | ||
122 | poll: => | |
123 | @.out!.in\poll! | |
124 | false | |
125 | ||
126 | apply_range = (range, val) -> | |
127 | if range\type! == T.str | |
128 | switch range! | |
129 | when 'raw' then val | |
130 | when 'uni' then val / 128 | |
131 | when 'bip' then val / 64 - 1 | |
132 | when 'rad' then val / 64 * math.pi | |
133 | when 'deg' then val / 128 * 360 | |
134 | else | |
135 | error Error 'argument', "unknown range '#{range!}'" | |
136 | elseif range\type! == T.num | |
137 | val / 128 * range! | |
138 | else | |
139 | error Error 'argument', "range has to be a string or number" | |
140 | ||
141 | { | |
142 | :input | |
143 | :output | |
144 | :port | |
145 | :apply_range | |
146 | :bit | |
147 | } |
0 | 0 | import Constant, Op, Input, T, Struct, sig, evt from require 'alv.base' |
1 | import input, output, port, apply_range from require 'alv-lib.midi.core' | |
1 | import input, output, port, apply_range from require 'alv-lib._midi' | |
2 | 2 | import monotime from require 'system' |
3 | 3 | |
4 | 4 | gate = Constant.meta |
139 | 139 | summary: "`send MIDI note events." |
140 | 140 | examples: { '(midi/send-notes [port] [chan] note-events)' } |
141 | 141 | description: " |
142 | `chan` can be | |
143 | 142 | `note-events` is a !-stream of structs with the following keys: |
144 | 143 | |
145 | 144 | - `pitch`: MIDI pitch (num) |