midi-communications 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|