1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
import Constant, T, Struct, Op, Input, T, Error, const from require 'alv.base'
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
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
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 InPort
new: (@name) =>
@port = find_port RtMidiIn, @name
@msgs = {}
poll: =>
@msgs = while true
delta, bytes = @port\getmessage!
break unless delta
{ status, a, b } = bytes
chan = band status, 0xf
status = MIDI[rshift status, 4]
{ :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) =>
if 'string' == type 'status'
status = bor (lshift rMIDI[status], 4), chan
@port\sendmessage status, a, b
__tostring: => "[#{@name}]"
__tojson: => string.format '%q', tostring @
class PortOp extends Op
setup: (inputs) =>
super inputs
{ :inp, :out } = @unwrap_all!
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:
name: 'input'
summary: "Create a MIDI input port."
examples: { '(midi/input name)' }
value: class extends PortOp
setup: (inputs) =>
name = const.str\match inputs
super inp: Input.hot name
poll: =>
@.out!\poll!
false
output = Constant.meta
meta:
name: 'output'
summary: "Create a MIDI output port."
examples: { '(midi/output name)' }
value: class extends PortOp
setup: (inputs) =>
name = const.str\match inputs
super out: Input.hot name
port = Constant.meta
meta:
name: 'port'
summary: "Create a bidirectional MIDI port."
examples: { '(midi/port name)' }
value: class extends PortOp
setup: (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!
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! == T.num
val / 128 * range!
else
error Error 'argument', "range has to be a string or number"
{
:input
:output
:port
:apply_range
:bit
}
|