midi-communications 0.5.1
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/.gitignore +10 -0
- data/Gemfile +3 -0
- data/LICENSE +674 -0
- data/LICENSE.unimidi +13 -0
- data/README.md +149 -0
- data/Rakefile +44 -0
- data/examples/input.rb +24 -0
- data/examples/output.rb +24 -0
- data/examples/select_a_device.rb +57 -0
- data/examples/sysex_output.rb +14 -0
- data/lib/midi-communications/adapter/jruby.rb +22 -0
- data/lib/midi-communications/adapter/linux.rb +22 -0
- data/lib/midi-communications/adapter/macos.rb +22 -0
- data/lib/midi-communications/adapter/windows.rb +23 -0
- data/lib/midi-communications/device.rb +195 -0
- data/lib/midi-communications/input/stream_reader.rb +79 -0
- data/lib/midi-communications/input.rb +16 -0
- data/lib/midi-communications/loader.rb +29 -0
- data/lib/midi-communications/output.rb +60 -0
- data/lib/midi-communications/platform.rb +33 -0
- data/lib/midi-communications/type_conversion.rb +14 -0
- data/lib/midi-communications.rb +22 -0
- data/midi-communications.gemspec +31 -0
- metadata +146 -0
data/LICENSE.unimidi
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2010-2017 Ari Russo
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# MIDI Communications
|
2
|
+
|
3
|
+
**Platform independent realtime MIDI input and output for Ruby.**
|
4
|
+
|
5
|
+
This library is part of a suite of Ruby libraries for MIDI:
|
6
|
+
|
7
|
+
| Function | Library |
|
8
|
+
| --- | --- |
|
9
|
+
| MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) |
|
10
|
+
| MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) |
|
11
|
+
| MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) |
|
12
|
+
| Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) |
|
13
|
+
| Low level MIDI interface to Linux | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [alsa-rawmidi](http://github.com/arirusso/alsa-rawmidi)) |
|
14
|
+
| Low level MIDI interface to JRuby | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [midi-jruby](http://github.com/arirusso/midi-jruby))|
|
15
|
+
| Low level MIDI interface to Windows | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [midi-winm](http://github.com/arirusso/midi-winmm)) |
|
16
|
+
|
17
|
+
This library is based on [Ari Russo's](http://github.com/arirusso) library [UniMIDI](https://github.com/arirusso/unimidi).
|
18
|
+
|
19
|
+
### Features
|
20
|
+
|
21
|
+
* Supports OSX, Linux, JRuby, Windows and Cygwin
|
22
|
+
* No compilation required
|
23
|
+
* Both input and output to and from multiple devices concurrently
|
24
|
+
* Generalized handling of different MIDI and SysEx Message types
|
25
|
+
* On OSX use IAC to internally route MIDI to other programs
|
26
|
+
* No events history and no buffers optimization
|
27
|
+
|
28
|
+
### Requirements
|
29
|
+
|
30
|
+
**MIDI Communications** uses one of the following libraries, depending on which platform you're using it on. The necessary library should install automatically with the midi-communications gem.
|
31
|
+
|
32
|
+
Platform
|
33
|
+
|
34
|
+
* OSX: [midi-communications-macos](http://github.com/javier-sy/midi-communications-macos)
|
35
|
+
* JRuby: [midi-jruby](http://github.com/arirusso/midi-jruby) (**TODO: update to midi-communications-jruby**)
|
36
|
+
* Linux: [alsa-rawmidi](http://github.com/arirusso/alsa-rawmidi) (**TODO: update to midi-communications-linux**)
|
37
|
+
* Windows/Cygwin: [midi-winmm](http://github.com/arirusso/midi-winmm) (**TODO: update to midi-communications-windows**)
|
38
|
+
|
39
|
+
### Install
|
40
|
+
|
41
|
+
If you're using Bundler, add this line to your application's Gemfile:
|
42
|
+
|
43
|
+
`gem "midi-communications"`
|
44
|
+
|
45
|
+
Otherwise...
|
46
|
+
|
47
|
+
`gem install midi-communications`
|
48
|
+
|
49
|
+
### Usage
|
50
|
+
|
51
|
+
Some examples are included with the library:
|
52
|
+
|
53
|
+
* [Selecting a device](http://github.com/arirusso/javier-sy/midi-communications/blob/master/examples/select_a_device.rb)
|
54
|
+
* [MIDI input](http://github.com/javier-sy/midi-communications/blob/master/examples/input.rb)
|
55
|
+
* [MIDI output](http://github.com/javier-sy/midi-communications/blob/master/examples/output.rb)
|
56
|
+
* [MIDI Sysex output](http://github.com/javier-sy/midi-communications/blob/master/examples/sysex_output.rb)
|
57
|
+
|
58
|
+
### Tests
|
59
|
+
|
60
|
+
**MIDI Communications** includes a set of tests which assume that an output is connected to an input. You will be asked to select which input and output as the test is run.
|
61
|
+
|
62
|
+
The tests can be run using:
|
63
|
+
|
64
|
+
`rake test`
|
65
|
+
|
66
|
+
See below for additional notes on testing with JRuby.
|
67
|
+
|
68
|
+
### Documentation
|
69
|
+
|
70
|
+
[rdoc](http://rdoc.info/gems/midi-communications) (**TODO**)
|
71
|
+
|
72
|
+
### Platform Specific Notes
|
73
|
+
|
74
|
+
##### JRuby
|
75
|
+
|
76
|
+
* (**TO CONFIRM**) You must be in 1.9 mode. This is normally accomplished by passing --1.9 to JRuby at the command line. For testing in 1.9 mode, use `jruby --1.9 -S rake test`
|
77
|
+
* (**TO CONFIRM**) javax.sound has some documented issues with SysEx messages in some versions OSX Snow Leopard which do affect this library.
|
78
|
+
|
79
|
+
##### Linux
|
80
|
+
|
81
|
+
* (**TO CONFIRM**) *libasound* and *libasound-dev* packages are required
|
82
|
+
|
83
|
+
## Differences between [MIDI Communications](https://github.com/javier-sy/midi-communications) library and [UniMIDI](https://github.com/arirusso/unimidi) library
|
84
|
+
|
85
|
+
[MIDI Communications](https://github.com/javier-sy/midi-communications) is mostly a clone of [UniMIDI](https://github.com/arirusso/unimidi) with some modifications:
|
86
|
+
* Uses [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) instead of [ffi-coremidi](https://github.com/arirusso/ffi-coremidi)
|
87
|
+
* Removed buffering (to reduce CPU usage in some scenarios)
|
88
|
+
* Source updated to Ruby 2.7 code conventions (method keyword parameters instead of options = {}, hash keys as 'key:' instead of ':key =>', etc.)
|
89
|
+
* Updated dependencies versions
|
90
|
+
* Renamed module to MIDICommunications instead of UniMIDI
|
91
|
+
* Renamed gem to midi-communications instead of unimidi
|
92
|
+
* TODO: update tests to use rspec instead of rake
|
93
|
+
* TODO: migrate to (or confirm it's working ok on) Ruby 3.0 and Ruby 3.1
|
94
|
+
|
95
|
+
## Then, why does exist this library if it is mostly a clone of another library?
|
96
|
+
|
97
|
+
The author has been developing since 2016 a Ruby project called
|
98
|
+
[Musa DSL](https://github.com/javier-sy/musa-dsl) that needs a way
|
99
|
+
of representing MIDI Events and a way of communicating with
|
100
|
+
MIDI Instruments and MIDI Control Surfaces.
|
101
|
+
|
102
|
+
[Ari Russo](https://github.com/arirusso) has done a great job creating
|
103
|
+
several interdependent Ruby libraries that allow
|
104
|
+
MIDI Events representation ([MIDI Message](https://github.com/arirusso/midi-message)
|
105
|
+
and [Nibbler](https://github.com/arirusso/nibbler))
|
106
|
+
and communication with MIDI Instruments and MIDI Control Surfaces
|
107
|
+
([unimidi](https://github.com/arirusso/unimidi),
|
108
|
+
[ffi-coremidi](https://github.com/arirusso/ffi-coremidi) and others)
|
109
|
+
that, **with some modifications**, I've been using in MusaDSL.
|
110
|
+
|
111
|
+
After thinking about the best approach to publish MusaDSL
|
112
|
+
I've decided to publish my own renamed versions of the modified dependencies because:
|
113
|
+
|
114
|
+
* The original libraries have features
|
115
|
+
(buffering, very detailed logging and processing history information, not locking behaviour when waiting input midi messages)
|
116
|
+
that are not needed in MusaDSL and, in fact,
|
117
|
+
can degrade the performance on some use cases in MusaDSL.
|
118
|
+
* The requirements for **Musa DSL** users probably will evolve in time, so it will be easier to maintain an independent source code base.
|
119
|
+
* Some differences on the approach of the modifications vs the original library doesn't allow to merge the modifications on the original libraries.
|
120
|
+
* Then the renaming of the libraries is needed to avoid confusing existent users of the original libraries.
|
121
|
+
* Due to some of the interdependencies of Ari Russo libraries,
|
122
|
+
the modification and renaming on some of the low level libraries (ffi-coremidi, etc.)
|
123
|
+
forces to modify and rename unimidi library.
|
124
|
+
|
125
|
+
All in all I have decided to publish a suite of libraries optimized for MusaDSL use case that also can be used by other people in their projects.
|
126
|
+
|
127
|
+
| Function | Library | Based on Ari Russo's| Difference |
|
128
|
+
| --- | --- | --- | --- |
|
129
|
+
| MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) | [MIDI Message](https://github.com/arirusso/midi-message) | removed parsing, small improvements |
|
130
|
+
| MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) | [Nibbler](https://github.com/arirusso/nibbler) | removed process history information, minor optimizations |
|
131
|
+
| MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) | [unimidi](https://github.com/arirusso/unimidi) | use of [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos, removed process history information, removed buffering, removed command line script)
|
132
|
+
| Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) | [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) | removed buffering and process history information, locking behaviour when waiting midi events, improved midi devices name detection, minor optimizations |
|
133
|
+
| Low level MIDI interface to Linux | **TO DO** | | |
|
134
|
+
| Low level MIDI interface to JRuby | **TO DO** | | |
|
135
|
+
| Low level MIDI interface to Windows | **TO DO** | | |
|
136
|
+
|
137
|
+
## Author
|
138
|
+
|
139
|
+
* [Javier Sánchez Yeste](https://github.com/javier-sy)
|
140
|
+
|
141
|
+
## Acknowledgements
|
142
|
+
|
143
|
+
Thanks to [Ari Russo](http://github.com/arirusso) for his ruby library [unimidi](https://github.com/arirusso/unimidi) licensed under Apache License 2.0.
|
144
|
+
|
145
|
+
### License
|
146
|
+
|
147
|
+
[MIDI Communications](https://github.com/javier-sy/midi-communications) Copyright (c) 2021 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
|
148
|
+
|
149
|
+
[unimidi](https://github.com/arirusso/unimidi) Copyright (c) 2010-2017 [Ari Russo](http://arirusso.com), licensed under Apache License 2.0 (see the file LICENSE.unimidi)
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
$LOAD_PATH.prepend __dir__
|
2
|
+
$LOAD_PATH.prepend File.join(__dir__, 'lib')
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'midi-communications'
|
7
|
+
|
8
|
+
namespace(:test) do
|
9
|
+
task all: [:unit, :integration]
|
10
|
+
|
11
|
+
Rake::TestTask.new(:integration) do |t|
|
12
|
+
t.libs << 'test'
|
13
|
+
t.test_files = FileList['test/integration/**/*_test.rb']
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
Rake::TestTask.new(:unit) do |t|
|
18
|
+
t.libs << 'test'
|
19
|
+
t.test_files = FileList['test/unit/**/*_test.rb']
|
20
|
+
t.verbose = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Rake::Task['test'].enhance ['test:all']
|
25
|
+
|
26
|
+
platforms = ['generic', 'x86_64-darwin10.7.0', 'i386-mingw32', 'java', 'i686-linux']
|
27
|
+
|
28
|
+
task(:build) do
|
29
|
+
require 'midi-communications-gemspec'
|
30
|
+
platforms.each do |platform|
|
31
|
+
MIDICommunications::Gemspec.new(platform)
|
32
|
+
filename = "midi-communications-#{platform}.gemspec"
|
33
|
+
system "gem build #{filename}"
|
34
|
+
system "rm #{filename}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
task(release: :build) do
|
39
|
+
platforms.each do |platform|
|
40
|
+
system "gem push midi-communications-#{MIDICommunications::VERSION}-#{platform}.gem"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task(default: [:test])
|
data/examples/input.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.prepend(File.expand_path('../lib', __dir__))
|
4
|
+
|
5
|
+
require 'midi-communications'
|
6
|
+
|
7
|
+
# Prompts the user to select a midi input
|
8
|
+
# Sends an inspection of the first 10 messages messages that input receives to standard out
|
9
|
+
|
10
|
+
num_messages = 10
|
11
|
+
|
12
|
+
# Prompt the user
|
13
|
+
input = MIDICommunications::Input.gets
|
14
|
+
|
15
|
+
# using their selection...
|
16
|
+
|
17
|
+
puts 'send some MIDI to your input now...'
|
18
|
+
|
19
|
+
num_messages.times do
|
20
|
+
m = input.gets
|
21
|
+
puts(m)
|
22
|
+
end
|
23
|
+
|
24
|
+
puts 'finished'
|
data/examples/output.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.prepend(File.expand_path('../lib', __dir__))
|
4
|
+
|
5
|
+
require 'midi-communications'
|
6
|
+
|
7
|
+
# Prompts the user to select a midi output
|
8
|
+
# Sends some arpeggiated chords to the output
|
9
|
+
|
10
|
+
notes = [36, 40, 43] # C E G
|
11
|
+
octaves = 5
|
12
|
+
duration = 0.1
|
13
|
+
|
14
|
+
# Prompt the user to select an output
|
15
|
+
output = MIDICommunications::Output.gets
|
16
|
+
|
17
|
+
# using their selection...
|
18
|
+
(0..((octaves-1)*12)).step(12) do |oct|
|
19
|
+
notes.each do |note|
|
20
|
+
output.puts(0x90, note + oct, 100) # note on
|
21
|
+
sleep(duration) # wait
|
22
|
+
output.puts(0x80, note + oct, 100) # note off
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.prepend(File.expand_path('../lib', __dir__))
|
4
|
+
|
5
|
+
require 'midi-communications'
|
6
|
+
|
7
|
+
#
|
8
|
+
# This is an example that explains how to select an output.
|
9
|
+
# It's not really meant to be run.
|
10
|
+
#
|
11
|
+
|
12
|
+
# Prompt the user for selection in the console
|
13
|
+
|
14
|
+
output = MIDICommunications::Output.gets
|
15
|
+
|
16
|
+
# The user will see a list that reflects their local MIDI configuration, and be prompted to select a number
|
17
|
+
|
18
|
+
# Select a MIDI output
|
19
|
+
# 1) IAC Device
|
20
|
+
# 2) Roland UM-2 (1)
|
21
|
+
# 3) Roland UM-2 (2)
|
22
|
+
# >
|
23
|
+
|
24
|
+
# Once they've selected, the device that corresponds with their selection is returned.
|
25
|
+
|
26
|
+
# (Note that it's returned open so you don't need to call output.open)
|
27
|
+
|
28
|
+
# Hard-code the selection like this
|
29
|
+
|
30
|
+
output = MIDICommunications::Output.use(:first)
|
31
|
+
output = MIDICommunications::Output.use(0)
|
32
|
+
|
33
|
+
# or
|
34
|
+
|
35
|
+
output = MIDICommunications::Output.open(:first)
|
36
|
+
output = MIDICommunications::Output.open(0)
|
37
|
+
|
38
|
+
# If you want to wait to open the device, you can select it with any of these "finder" methods
|
39
|
+
|
40
|
+
output = MIDICommunications::Output.first
|
41
|
+
output = MIDICommunications::Output[0]
|
42
|
+
output = MIDICommunications::Output.all[0]
|
43
|
+
output = MIDICommunications::Output.all.first
|
44
|
+
output = MIDICommunications::Device.all_by_type(:output)[0]
|
45
|
+
output = MIDICommunications::Device.all_by_type(:output).first
|
46
|
+
|
47
|
+
# You'll need to call open on these before you use it or an exception will be raised
|
48
|
+
|
49
|
+
output.open
|
50
|
+
|
51
|
+
# It's also possible to select a device by name
|
52
|
+
|
53
|
+
output = MIDICommunications::Output.find_by_name('Roland UM-2 (1)').open
|
54
|
+
|
55
|
+
# or using regex match
|
56
|
+
|
57
|
+
output = MIDICommunications::Output.find { |device| device.name.match(/Launchpad/) }.open(:first)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.prepend(File.expand_path('../lib', __dir__))
|
4
|
+
|
5
|
+
require 'midi-communications'
|
6
|
+
|
7
|
+
# Prompts the user to select a MIDI output
|
8
|
+
# Sends a MIDI system exclusive message to that output
|
9
|
+
|
10
|
+
sysex_msg = [0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7]
|
11
|
+
|
12
|
+
output = MIDICommunications::Output.gets
|
13
|
+
|
14
|
+
output.puts(sysex_msg)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'midi-jruby'
|
2
|
+
|
3
|
+
module MIDICommunications
|
4
|
+
module Adapter
|
5
|
+
# Load underlying devices using the midi-jruby gem
|
6
|
+
module JRuby
|
7
|
+
module Loader
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# @return [Array<JRuby::Input>]
|
11
|
+
def inputs
|
12
|
+
::MIDIJRuby::Device.all_by_type[:input]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<JRuby::Output>]
|
16
|
+
def outputs
|
17
|
+
::MIDIJRuby::Device.all_by_type[:output]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'alsa-rawmidi'
|
2
|
+
|
3
|
+
module MIDICommunications
|
4
|
+
module Adapter
|
5
|
+
# Load underlying devices using the alsa-rawmidi gem
|
6
|
+
module Linux
|
7
|
+
module Loader
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# @return [Array<Linux::Input>]
|
11
|
+
def inputs
|
12
|
+
::AlsaRawMIDI::Device.all_by_type[:input]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<Linux::Output>]
|
16
|
+
def outputs
|
17
|
+
::AlsaRawMIDI::Device.all_by_type[:output]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'midi-communications-macos'
|
2
|
+
|
3
|
+
module MIDICommunications
|
4
|
+
module Adapter
|
5
|
+
# Load underlying devices using the coremidi gem
|
6
|
+
module MacOS
|
7
|
+
module Loader
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# @return [Array<MacOS::Source>]
|
11
|
+
def inputs
|
12
|
+
::MIDICommunicationsMacOS::Endpoint.all_by_type[:source]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<MacOS::Destination>]
|
16
|
+
def outputs
|
17
|
+
::MIDICommunicationsMacOS::Endpoint.all_by_type[:destination]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'midi-winmm'
|
2
|
+
|
3
|
+
module MIDICommunications
|
4
|
+
module Adapter
|
5
|
+
# Load underlying devices using the midi-winmm gem
|
6
|
+
module Windows
|
7
|
+
module Loader
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# @return [Array<Windows::Input>]
|
12
|
+
def inputs
|
13
|
+
::MIDIWinMM::Device.all_by_type[:input]
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Array<Windows::Output>]
|
17
|
+
def outputs
|
18
|
+
::MIDIWinMM::Device.all_by_type[:output]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module MIDICommunications
|
2
|
+
# Common logic that is shared by both Input and Output devices
|
3
|
+
module Device
|
4
|
+
# Methods that are shared by both Input and Output classes
|
5
|
+
module ClassMethods
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Iterate over all devices of this direction (eg Input, Output)
|
9
|
+
def each(&block)
|
10
|
+
all.each(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Prints ids and names of each device to the console
|
14
|
+
# @return [Array<String>]
|
15
|
+
def list
|
16
|
+
all.map do |device|
|
17
|
+
name = "#{device.id}) #{device.display_name}"
|
18
|
+
puts(name)
|
19
|
+
name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Shortcut to select a device by its name
|
24
|
+
# @param [String, Symbol] name
|
25
|
+
# @return [Input, Output]
|
26
|
+
def find_by_name(name)
|
27
|
+
all.find { |device| name.to_s == device.name }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Streamlined console prompt that asks the user to select a device
|
31
|
+
# When their input is received, the device is selected and enabled
|
32
|
+
def gets(&block)
|
33
|
+
device = nil
|
34
|
+
direction = get_direction
|
35
|
+
puts ''
|
36
|
+
puts "Select a MIDI #{direction}..."
|
37
|
+
while device.nil?
|
38
|
+
list
|
39
|
+
print '> '
|
40
|
+
selection = $stdin.gets.chomp
|
41
|
+
if selection != ''
|
42
|
+
selection = Integer(selection) rescue nil
|
43
|
+
device = all.find { |d| d.id == selection } unless selection.nil?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
device.open(&block)
|
47
|
+
device
|
48
|
+
end
|
49
|
+
|
50
|
+
# Select the first device and enable it
|
51
|
+
# @return [Input, Output]
|
52
|
+
def first(&block)
|
53
|
+
use_device(all.first, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Select the last device and enable it
|
57
|
+
# @return [Input, Output]
|
58
|
+
def last(&block)
|
59
|
+
use_device(all.last, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Select the device at the given index and enable it
|
63
|
+
# @param [Integer] index
|
64
|
+
# @return [Input, Output]
|
65
|
+
def use(index, &block)
|
66
|
+
index = case index
|
67
|
+
when :first then 0
|
68
|
+
when :last then all.count - 1
|
69
|
+
else index
|
70
|
+
end
|
71
|
+
use_device(at(index), &block)
|
72
|
+
end
|
73
|
+
alias open use
|
74
|
+
|
75
|
+
# Select the device at the given index
|
76
|
+
# @param [Integer] index
|
77
|
+
# @return [Input, Output]
|
78
|
+
def at(index)
|
79
|
+
all[index]
|
80
|
+
end
|
81
|
+
alias [] at
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# The direction of the device eg "input", "output"
|
86
|
+
# @return [String]
|
87
|
+
def get_direction
|
88
|
+
name.split('::').last.downcase
|
89
|
+
end
|
90
|
+
|
91
|
+
# Enable the given device
|
92
|
+
# @param [Input, Output] device
|
93
|
+
# @return [Input, Output]
|
94
|
+
def use_device(device, &block)
|
95
|
+
if device.enabled?
|
96
|
+
yield(device) if block_given?
|
97
|
+
else
|
98
|
+
device.open(&block)
|
99
|
+
end
|
100
|
+
device
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Methods that are shared by both Input and Output instances
|
105
|
+
module InstanceMethods
|
106
|
+
# @param [AlsaRawMIDI::Input, AlsaRawMIDI::Output, MIDICommunicationsMacOS::Destination, MIDICommunicationsMacOS::Source, MIDIJRuby::Input, MIDIJRuby::Output, MIDIWinMM::Input, MIDIWinMM::Output] device
|
107
|
+
def initialize(device)
|
108
|
+
@device = device
|
109
|
+
@enabled = false
|
110
|
+
|
111
|
+
populate_from_device
|
112
|
+
end
|
113
|
+
|
114
|
+
# Enable the device for use
|
115
|
+
# Params are passed to the underlying device object
|
116
|
+
# Can be passed a block to which the device will be passed in as the yieldparam
|
117
|
+
# @param [*Object] args
|
118
|
+
# @return [Input, Output] self
|
119
|
+
def open(*args)
|
120
|
+
unless @enabled
|
121
|
+
@device.open(*args)
|
122
|
+
@enabled = true
|
123
|
+
end
|
124
|
+
if block_given?
|
125
|
+
begin
|
126
|
+
yield(self)
|
127
|
+
ensure
|
128
|
+
close
|
129
|
+
end
|
130
|
+
else
|
131
|
+
at_exit do
|
132
|
+
close
|
133
|
+
end
|
134
|
+
end
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# Close the device
|
139
|
+
# Params are passed to the underlying device object
|
140
|
+
# @param [*Object] args
|
141
|
+
# @return [Boolean]
|
142
|
+
def close(*args)
|
143
|
+
if @enabled
|
144
|
+
@device.close(*args)
|
145
|
+
@enabled = false
|
146
|
+
true
|
147
|
+
else
|
148
|
+
false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns true if the device is not enabled
|
153
|
+
# @return [Boolean]
|
154
|
+
def closed?
|
155
|
+
!@enabled
|
156
|
+
end
|
157
|
+
|
158
|
+
# Add attributes for the device instance
|
159
|
+
# :direction, :id, :name
|
160
|
+
def self.included(base)
|
161
|
+
base.send(:attr_reader, :direction)
|
162
|
+
base.send(:attr_reader, :enabled)
|
163
|
+
base.send(:attr_reader, :id)
|
164
|
+
base.send(:attr_reader, :manufacturer)
|
165
|
+
base.send(:attr_reader, :model)
|
166
|
+
base.send(:attr_reader, :name)
|
167
|
+
base.send(:attr_reader, :display_name)
|
168
|
+
base.send(:alias_method, :enabled?, :enabled)
|
169
|
+
base.send(:alias_method, :type, :direction)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
# Populate the direction attribute
|
175
|
+
def populate_direction
|
176
|
+
@direction = case @device.type
|
177
|
+
when :source, :input then :input
|
178
|
+
when :destination, :output then :output
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Populate attributes from the underlying device object
|
183
|
+
def populate_from_device
|
184
|
+
@id = @device.id
|
185
|
+
|
186
|
+
@manufacturer = @device.manufacturer
|
187
|
+
@model = @device.model
|
188
|
+
@name = @device.name
|
189
|
+
@display_name = @device.display_name
|
190
|
+
|
191
|
+
populate_direction
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|