rtmidi-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +327 -0
- data/Rakefile +8 -0
- data/examples/list_ports.rb +16 -0
- data/examples/receive_callback.rb +25 -0
- data/examples/receive_polling.rb +27 -0
- data/examples/send_note.rb +18 -0
- data/examples/sysex_send.rb +20 -0
- data/examples/virtual_port.rb +19 -0
- data/lib/rtmidi/api.rb +73 -0
- data/lib/rtmidi/error.rb +52 -0
- data/lib/rtmidi/message.rb +196 -0
- data/lib/rtmidi/midi_in.rb +291 -0
- data/lib/rtmidi/midi_out.rb +361 -0
- data/lib/rtmidi/native.rb +198 -0
- data/lib/rtmidi/version.rb +5 -0
- data/lib/rtmidi.rb +9 -0
- metadata +77 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a7893d826f1f417ce7a5b056e0a008ac45f3ff3f42fb1efab5444df197925433
|
|
4
|
+
data.tar.gz: 436013485d4e4faf7f33eaddf01b42422abba0ede737a6a2df49c9969eeed67a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5ab907249073716156bfc7638d69c1967f5c7fb2a21eb5fbd8545317d3964862bb71792c4354198373ea94e08b3c1152bff3ecec62a2f3580299831d178e7944
|
|
7
|
+
data.tar.gz: 94cb6a7900b92b1efe38ada1f0d717b55273b2be13497c8f55e644ba5c6dd8aa37b2525a559a49d84c566c903d83d8c3026e762d57ac9e5baa8aed493f5037cb
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# rtmidi-ruby
|
|
2
|
+
|
|
3
|
+
`rtmidi-ruby` is a Ruby FFI binding for the RtMidi C API (`rtmidi_c.h`).
|
|
4
|
+
It exposes the low-level C bindings through `Rtmidi::Native` and a higher-level
|
|
5
|
+
Ruby API through `Rtmidi::MidiIn`, `Rtmidi::MidiOut`, and `Rtmidi::Message`.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- `Rtmidi.version`, `Rtmidi.compiled_apis`, and API name helpers
|
|
10
|
+
- low-level access to the RtMidi C API via `Rtmidi::Native`
|
|
11
|
+
- `Rtmidi::MidiIn` with callback and polling based input
|
|
12
|
+
- `Rtmidi::MidiOut` helpers for channel, system common, and realtime messages
|
|
13
|
+
- typed MIDI messages through `Rtmidi::Message`
|
|
14
|
+
- virtual port support
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Ruby 3.1+
|
|
19
|
+
- a system `librtmidi` that provides the RtMidi C API
|
|
20
|
+
|
|
21
|
+
Install `librtmidi` with your package manager:
|
|
22
|
+
|
|
23
|
+
- macOS: `brew install rtmidi`
|
|
24
|
+
- Ubuntu/Debian: `sudo apt install librtmidi-dev`
|
|
25
|
+
- Fedora: `sudo dnf install rtmidi-devel`
|
|
26
|
+
- Arch: `sudo pacman -S rtmidi`
|
|
27
|
+
- Windows: install an RtMidi DLL and make sure it is on `PATH`
|
|
28
|
+
|
|
29
|
+
If the library is installed in a non-standard location, set `RTMIDI_LIB_PATH`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
RTMIDI_LIB_PATH=/path/to/librtmidi.dylib bundle exec ruby your_script.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Some `librtmidi` builds do not expose `rtmidi_set_error_callback`. In that case,
|
|
36
|
+
normal MIDI I/O still works, but `on_error` callbacks are unavailable.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Add the gem to your Gemfile:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
gem "rtmidi-ruby"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then install dependencies:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or install the gem directly:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gem install rtmidi-ruby
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
require "rtmidi"
|
|
62
|
+
|
|
63
|
+
puts "RtMidi version: #{Rtmidi.version}"
|
|
64
|
+
puts "Compiled APIs: #{Rtmidi.compiled_apis.inspect}"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### List Ports
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
require "rtmidi"
|
|
71
|
+
|
|
72
|
+
out = Rtmidi::MidiOut.new
|
|
73
|
+
puts "Output ports:"
|
|
74
|
+
out.port_names.each_with_index { |name, index| puts " #{index}: #{name}" }
|
|
75
|
+
out.close
|
|
76
|
+
|
|
77
|
+
input = Rtmidi::MidiIn.new
|
|
78
|
+
puts "Input ports:"
|
|
79
|
+
input.port_names.each_with_index { |name, index| puts " #{index}: #{name}" }
|
|
80
|
+
input.close
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Send Note On/Off
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
require "rtmidi"
|
|
87
|
+
|
|
88
|
+
out = Rtmidi::MidiOut.new
|
|
89
|
+
|
|
90
|
+
if out.port_count.zero?
|
|
91
|
+
warn "No output ports available."
|
|
92
|
+
else
|
|
93
|
+
begin
|
|
94
|
+
out.open_port(0)
|
|
95
|
+
out.note_on(0, 60, 100)
|
|
96
|
+
sleep 0.5
|
|
97
|
+
out.note_off(0, 60)
|
|
98
|
+
ensure
|
|
99
|
+
out.close
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Receive With Callback
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
require "rtmidi"
|
|
108
|
+
|
|
109
|
+
midi_in = Rtmidi::MidiIn.new
|
|
110
|
+
|
|
111
|
+
if midi_in.port_count.zero?
|
|
112
|
+
warn "No input ports available."
|
|
113
|
+
midi_in.close
|
|
114
|
+
exit 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
begin
|
|
118
|
+
midi_in.ignore_types(sysex: false, timing: false, active_sensing: false)
|
|
119
|
+
midi_in.open_port(0)
|
|
120
|
+
|
|
121
|
+
midi_in.on_message do |message, timestamp|
|
|
122
|
+
puts "#{timestamp}: #{message.map { |byte| format('%02X', byte) }.join(' ')}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
puts "Listening... (Ctrl-C to quit)"
|
|
126
|
+
sleep
|
|
127
|
+
ensure
|
|
128
|
+
midi_in&.close
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Receive With Polling
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
require "rtmidi"
|
|
136
|
+
|
|
137
|
+
midi_in = Rtmidi::MidiIn.new
|
|
138
|
+
|
|
139
|
+
if midi_in.port_count.zero?
|
|
140
|
+
warn "No input ports available."
|
|
141
|
+
midi_in.close
|
|
142
|
+
exit 0
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
begin
|
|
146
|
+
midi_in.open_port(0)
|
|
147
|
+
|
|
148
|
+
loop do
|
|
149
|
+
packet = midi_in.get_message
|
|
150
|
+
next if packet.nil?
|
|
151
|
+
|
|
152
|
+
message, timestamp = packet
|
|
153
|
+
puts "#{timestamp}: #{message.inspect}"
|
|
154
|
+
|
|
155
|
+
sleep 0.001
|
|
156
|
+
end
|
|
157
|
+
ensure
|
|
158
|
+
midi_in&.close
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Typed Messages
|
|
163
|
+
|
|
164
|
+
`Rtmidi::Message` can parse raw bytes into typed structs and `MidiOut#send_message`
|
|
165
|
+
accepts either raw byte arrays or typed messages.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
require "rtmidi"
|
|
169
|
+
|
|
170
|
+
message = Rtmidi::Message.parse([0x92, 64, 96])
|
|
171
|
+
p message
|
|
172
|
+
# => #<struct Rtmidi::Message::NoteOn channel=2, note=64, velocity=96>
|
|
173
|
+
|
|
174
|
+
out = Rtmidi::MidiOut.new
|
|
175
|
+
|
|
176
|
+
if out.port_count.zero?
|
|
177
|
+
warn "No output ports available."
|
|
178
|
+
else
|
|
179
|
+
begin
|
|
180
|
+
out.open_port(0)
|
|
181
|
+
out.send_message(Rtmidi::Message::ProgramChange.new(channel: 1, program: 10))
|
|
182
|
+
ensure
|
|
183
|
+
out.close
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
For typed input callbacks:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
midi_in.on_message(parsed: true) do |message, timestamp|
|
|
192
|
+
p [message.class, message, timestamp]
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### System/Common and Realtime Helpers
|
|
197
|
+
|
|
198
|
+
`Rtmidi::MidiOut` includes helpers for common output messages:
|
|
199
|
+
|
|
200
|
+
- `sysex`
|
|
201
|
+
- `control_change`
|
|
202
|
+
- `program_change`
|
|
203
|
+
- `pitch_bend`
|
|
204
|
+
- `channel_aftertouch`
|
|
205
|
+
- `poly_aftertouch`
|
|
206
|
+
- `time_code_quarter_frame`
|
|
207
|
+
- `song_position_pointer`
|
|
208
|
+
- `song_select`
|
|
209
|
+
- `tune_request`
|
|
210
|
+
- `timing_clock`
|
|
211
|
+
- `start`
|
|
212
|
+
- `continue`
|
|
213
|
+
- `stop`
|
|
214
|
+
- `active_sensing`
|
|
215
|
+
- `system_reset`
|
|
216
|
+
- `nrpn`
|
|
217
|
+
|
|
218
|
+
Example:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
require "rtmidi"
|
|
222
|
+
|
|
223
|
+
out = Rtmidi::MidiOut.new
|
|
224
|
+
|
|
225
|
+
if out.port_count.zero?
|
|
226
|
+
warn "No output ports available."
|
|
227
|
+
else
|
|
228
|
+
begin
|
|
229
|
+
out.open_port(0)
|
|
230
|
+
out.sysex([0x7D, 0x01])
|
|
231
|
+
out.song_select(3)
|
|
232
|
+
out.start
|
|
233
|
+
out.nrpn(0, 0x1234, 0x0567)
|
|
234
|
+
ensure
|
|
235
|
+
out.close
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Virtual Ports
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
require "rtmidi"
|
|
244
|
+
|
|
245
|
+
midi_in = Rtmidi::MidiIn.new
|
|
246
|
+
midi_out = Rtmidi::MidiOut.new
|
|
247
|
+
|
|
248
|
+
begin
|
|
249
|
+
midi_in.open_virtual_port(name: "Rtmidi Ruby Virtual In")
|
|
250
|
+
midi_out.open_virtual_port(name: "Rtmidi Ruby Virtual Out")
|
|
251
|
+
|
|
252
|
+
puts "Virtual ports opened."
|
|
253
|
+
sleep
|
|
254
|
+
ensure
|
|
255
|
+
midi_out&.close
|
|
256
|
+
midi_in&.close
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Low-Level C API
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
require "rtmidi"
|
|
264
|
+
|
|
265
|
+
handle = nil
|
|
266
|
+
|
|
267
|
+
begin
|
|
268
|
+
handle = Rtmidi::Native.rtmidi_out_create_default
|
|
269
|
+
Rtmidi::Native.check_error(handle)
|
|
270
|
+
|
|
271
|
+
count = Rtmidi::Native.rtmidi_get_port_count(handle)
|
|
272
|
+
Rtmidi::Native.check_error(handle)
|
|
273
|
+
|
|
274
|
+
puts "#{count} output ports found"
|
|
275
|
+
ensure
|
|
276
|
+
Rtmidi::Native.rtmidi_out_free(handle) if handle && !handle.null?
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Error Handling
|
|
281
|
+
|
|
282
|
+
Synchronous operations raise `Rtmidi::Error` subclasses where possible, including:
|
|
283
|
+
|
|
284
|
+
- `Rtmidi::NoDevicesError`
|
|
285
|
+
- `Rtmidi::InvalidPortError`
|
|
286
|
+
- `Rtmidi::InvalidUseError`
|
|
287
|
+
- `Rtmidi::DriverError`
|
|
288
|
+
|
|
289
|
+
Validation failures use `ArgumentError`.
|
|
290
|
+
|
|
291
|
+
If a callback raises, the exception is stored and surfaced on the next locked API call.
|
|
292
|
+
|
|
293
|
+
## Callback Notes
|
|
294
|
+
|
|
295
|
+
- Keep callback processing lightweight.
|
|
296
|
+
- For heavier work, pass the event to a `Queue` and handle it in another thread.
|
|
297
|
+
- Do not call `close` or `close_port` from inside an input callback.
|
|
298
|
+
- `parsed: true` yields `Rtmidi::Message::*` structs instead of raw byte arrays.
|
|
299
|
+
|
|
300
|
+
## Examples
|
|
301
|
+
|
|
302
|
+
The repository includes runnable examples in [`examples/`](examples):
|
|
303
|
+
|
|
304
|
+
- `examples/list_ports.rb`
|
|
305
|
+
- `examples/send_note.rb`
|
|
306
|
+
- `examples/sysex_send.rb`
|
|
307
|
+
- `examples/receive_callback.rb`
|
|
308
|
+
- `examples/receive_polling.rb`
|
|
309
|
+
- `examples/virtual_port.rb`
|
|
310
|
+
|
|
311
|
+
Run them against the local checkout with:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
bundle exec ruby -Ilib examples/list_ports.rb
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Development
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
bundle install
|
|
321
|
+
bundle exec rspec
|
|
322
|
+
bundle exec rake
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
Released under the MIT License. See `LICENSE.txt`.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rtmidi"
|
|
4
|
+
|
|
5
|
+
puts "RtMidi version: #{Rtmidi.version}"
|
|
6
|
+
puts "Compiled APIs: #{Rtmidi.compiled_apis.inspect}"
|
|
7
|
+
|
|
8
|
+
out = Rtmidi::MidiOut.new
|
|
9
|
+
puts "Output ports:"
|
|
10
|
+
out.port_names.each_with_index { |name, index| puts " #{index}: #{name}" }
|
|
11
|
+
out.close
|
|
12
|
+
|
|
13
|
+
input = Rtmidi::MidiIn.new
|
|
14
|
+
puts "Input ports:"
|
|
15
|
+
input.port_names.each_with_index { |name, index| puts " #{index}: #{name}" }
|
|
16
|
+
input.close
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rtmidi"
|
|
4
|
+
|
|
5
|
+
midi_in = Rtmidi::MidiIn.new
|
|
6
|
+
|
|
7
|
+
if midi_in.port_count.zero?
|
|
8
|
+
warn "No input ports available."
|
|
9
|
+
midi_in.close
|
|
10
|
+
exit 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
midi_in.ignore_types(sysex: false, timing: false, active_sensing: false)
|
|
15
|
+
midi_in.open_port(0)
|
|
16
|
+
|
|
17
|
+
midi_in.on_message do |message, timestamp|
|
|
18
|
+
puts "#{timestamp}: #{message.map { |b| format('%02X', b) }.join(' ')}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts "Listening... (Ctrl-C to quit)"
|
|
22
|
+
sleep
|
|
23
|
+
ensure
|
|
24
|
+
midi_in&.close
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rtmidi"
|
|
4
|
+
|
|
5
|
+
midi_in = Rtmidi::MidiIn.new
|
|
6
|
+
|
|
7
|
+
if midi_in.port_count.zero?
|
|
8
|
+
warn "No input ports available."
|
|
9
|
+
midi_in.close
|
|
10
|
+
exit 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
midi_in.open_port(0)
|
|
15
|
+
|
|
16
|
+
loop do
|
|
17
|
+
packet = midi_in.get_message
|
|
18
|
+
if packet
|
|
19
|
+
message, timestamp = packet
|
|
20
|
+
puts "#{timestamp}: #{message.inspect}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
sleep 0.001
|
|
24
|
+
end
|
|
25
|
+
ensure
|
|
26
|
+
midi_in&.close
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rtmidi"
|
|
4
|
+
|
|
5
|
+
out = Rtmidi::MidiOut.new
|
|
6
|
+
|
|
7
|
+
if out.port_count.zero?
|
|
8
|
+
warn "No output ports available."
|
|
9
|
+
else
|
|
10
|
+
begin
|
|
11
|
+
out.open_port(0)
|
|
12
|
+
out.note_on(0, 60, 100)
|
|
13
|
+
sleep 0.5
|
|
14
|
+
out.note_off(0, 60)
|
|
15
|
+
ensure
|
|
16
|
+
out.close
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rtmidi"
|
|
4
|
+
|
|
5
|
+
# Example SysEx payload (manufacturer-specific example):
|
|
6
|
+
# F0 7D 10 01 F7
|
|
7
|
+
message = [0xF0, 0x7D, 0x10, 0x01, 0xF7]
|
|
8
|
+
|
|
9
|
+
out = Rtmidi::MidiOut.new
|
|
10
|
+
|
|
11
|
+
if out.port_count.zero?
|
|
12
|
+
warn "No output ports available."
|
|
13
|
+
else
|
|
14
|
+
begin
|
|
15
|
+
out.open_port(0)
|
|
16
|
+
out.send_message(message)
|
|
17
|
+
ensure
|
|
18
|
+
out.close
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rtmidi"
|
|
4
|
+
|
|
5
|
+
midi_in = Rtmidi::MidiIn.new
|
|
6
|
+
midi_out = Rtmidi::MidiOut.new
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
midi_in.open_virtual_port(name: "Rtmidi Ruby Virtual In")
|
|
10
|
+
midi_out.open_virtual_port(name: "Rtmidi Ruby Virtual Out")
|
|
11
|
+
|
|
12
|
+
puts "Virtual ports opened."
|
|
13
|
+
puts "Connect them from your MIDI patchbay / DAW, then send/receive as needed."
|
|
14
|
+
|
|
15
|
+
sleep
|
|
16
|
+
ensure
|
|
17
|
+
midi_out&.close
|
|
18
|
+
midi_in&.close
|
|
19
|
+
end
|
data/lib/rtmidi/api.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rtmidi
|
|
4
|
+
module Api
|
|
5
|
+
VALUES = {
|
|
6
|
+
unspecified: 0,
|
|
7
|
+
macosx_core: 1,
|
|
8
|
+
linux_alsa: 2,
|
|
9
|
+
unix_jack: 3,
|
|
10
|
+
windows_mm: 4,
|
|
11
|
+
rtmidi_dummy: 5,
|
|
12
|
+
web_midi_api: 6,
|
|
13
|
+
windows_uwp: 7,
|
|
14
|
+
android_amidi: 8,
|
|
15
|
+
num_apis: 9
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
NUMBERS = VALUES.invert.freeze
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def normalize(api)
|
|
22
|
+
case api
|
|
23
|
+
when Symbol
|
|
24
|
+
symbol_for(api)
|
|
25
|
+
when Integer
|
|
26
|
+
symbol_for(api)
|
|
27
|
+
else
|
|
28
|
+
nil
|
|
29
|
+
end || raise(ArgumentError, "unknown API: #{api.inspect}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def symbol_for(api)
|
|
33
|
+
return api if api.is_a?(Symbol) && VALUES.key?(api)
|
|
34
|
+
return NUMBERS[api] if api.is_a?(Integer)
|
|
35
|
+
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
def version
|
|
43
|
+
Native.rtmidi_get_version
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def compiled_apis
|
|
47
|
+
Native.ensure_loaded!
|
|
48
|
+
|
|
49
|
+
count = Native.rtmidi_get_compiled_api(nil, 0).to_i
|
|
50
|
+
return [] if count <= 0
|
|
51
|
+
|
|
52
|
+
buffer = FFI::MemoryPointer.new(:int, count)
|
|
53
|
+
written = Native.rtmidi_get_compiled_api(buffer, count).to_i
|
|
54
|
+
length = [written, count].min
|
|
55
|
+
return [] if length <= 0
|
|
56
|
+
|
|
57
|
+
buffer.read_array_of_int(length).filter_map { |value| Api.symbol_for(value) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def api_name(api)
|
|
61
|
+
Native.rtmidi_api_name(Api.normalize(api))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def api_display_name(api)
|
|
65
|
+
Native.rtmidi_api_display_name(Api.normalize(api))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def api_by_name(name)
|
|
69
|
+
value = Native.rtmidi_compiled_api_by_name(name.to_s)
|
|
70
|
+
Api.symbol_for(value) || :unspecified
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/rtmidi/error.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rtmidi
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :type
|
|
6
|
+
|
|
7
|
+
def initialize(message = "RtMidi error", type: :unspecified)
|
|
8
|
+
@type = type.to_sym
|
|
9
|
+
super(message)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class NoDevicesError < Error; end
|
|
14
|
+
class InvalidPortError < Error; end
|
|
15
|
+
class DriverError < Error; end
|
|
16
|
+
class MemoryError < Error; end
|
|
17
|
+
class InvalidUseError < Error; end
|
|
18
|
+
|
|
19
|
+
ERROR_MAP = {
|
|
20
|
+
no_devices_found: NoDevicesError,
|
|
21
|
+
invalid_device: InvalidPortError,
|
|
22
|
+
memory_error: MemoryError,
|
|
23
|
+
invalid_parameter: ArgumentError,
|
|
24
|
+
invalid_use: InvalidUseError,
|
|
25
|
+
driver_error: DriverError,
|
|
26
|
+
system_error: Error,
|
|
27
|
+
thread_error: Error,
|
|
28
|
+
warning: nil,
|
|
29
|
+
debug_warning: nil,
|
|
30
|
+
unspecified: Error
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
WARNING_TYPES = %i[warning debug_warning].freeze
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def warning_type?(type)
|
|
37
|
+
WARNING_TYPES.include?(type.to_sym)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def error_class_for(type)
|
|
41
|
+
ERROR_MAP.fetch(type.to_sym, Error)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def error_for(type, message)
|
|
45
|
+
klass = error_class_for(type)
|
|
46
|
+
return Error.new(message, type: type) if klass.nil?
|
|
47
|
+
return klass.new(message) unless klass <= Error
|
|
48
|
+
|
|
49
|
+
klass.new(message, type: type)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|