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
|
import Value, IO, Op, Registry, Input, Error, match from require 'core.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 IO
new: (@inp, @out) =>
@messages = {}
tick: =>
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, port: @ }
dirty: => #@messages > 0
receive: =>
coroutine.wrap ->
for msg in *@messages
coroutine.yield msg
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 'midi/port'
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
input = Value.meta
meta:
name: 'input'
summary: "Create a MIDI input port."
examples: { '(midi/input name)' }
value: class extends PortOp
setup: (inputs) =>
{ name } = match 'str', inputs
super name: Input.value name
tick: => super @inputs.name
output = Value.meta
meta:
name: 'output'
summary: "Create a MIDI output port."
examples: { '(midi/output name)' }
value: class extends PortOp
setup: (inputs) =>
{ name } = match 'str', inputs
super name: Input.value name
tick: => super nil, @inputs.name
inout = Value.meta
meta:
name: 'inout'
summary: "Create a bidirectional MIDI port."
examples: { '(midi/inout name)' }
value: class extends PortOp
setup: (inputs) =>
{ inp, out } = match 'str str', inputs
super
inp: Input.value inp
out: Input.value out
tick: => super @inputs.inp, @inputs.out
apply_range = (range, val) ->
if range\type! == 'str'
switch range\unwrap!
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 "unknown range #{range}"
elseif range.type == 'num'
val / 128 * range\unwrap!
else
error "range has to be a string or number"
{
:input
:output
:inout
:apply_range
:bit
}
|