midi-jruby 0.0.12
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.
- data/LICENSE +13 -0
- data/README.rdoc +52 -0
- data/lib/midi-jruby/device.rb +73 -0
- data/lib/midi-jruby/input.rb +190 -0
- data/lib/midi-jruby/output.rb +82 -0
- data/lib/midi-jruby.rb +15 -0
- metadata +62 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2010-2011 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.rdoc
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
= midi-jruby
|
2
|
+
|
3
|
+
== Summary
|
4
|
+
|
5
|
+
Simple MIDI wrapper for realtime IO in JRuby. Uses javax.sound.midi.
|
6
|
+
|
7
|
+
In the interest of allowing people on other platforms to utilize your code, you should consider using {unimidi}[http://github.com/arirusso/unimidi]. Unimidi is a platform independent wrapper which implements midi-jruby for users who are using jruby.
|
8
|
+
|
9
|
+
== Features
|
10
|
+
|
11
|
+
* Input and output on multiple devices concurrently
|
12
|
+
* Agnostically handle different MIDI Message types (including SysEx)
|
13
|
+
* Timestamped input events
|
14
|
+
|
15
|
+
== Install
|
16
|
+
|
17
|
+
* gem install midi-jruby
|
18
|
+
|
19
|
+
== Usage
|
20
|
+
|
21
|
+
This library requires that JRuby be run in '1.9 mode'. This is normally done by passing --1.9 to JRuby at the command line
|
22
|
+
|
23
|
+
== Examples
|
24
|
+
|
25
|
+
* {input}[http://github.com/arirusso/midi-jruby/blob/master/examples/input.rb]
|
26
|
+
* {output}[http://github.com/arirusso/midi-jruby/blob/master/examples/output.rb]
|
27
|
+
|
28
|
+
== Issues
|
29
|
+
|
30
|
+
There is an issue that causes javax.sound.midi not to be able to send SysEx messages in some OSX Snow Leopard versions.
|
31
|
+
|
32
|
+
== Tests
|
33
|
+
|
34
|
+
Use
|
35
|
+
|
36
|
+
jruby --1.9 -S rake test
|
37
|
+
|
38
|
+
* please see {test/config.rb}[http://github.com/arirusso/midi-jruby/blob/master/test/config.rb] before running tests
|
39
|
+
|
40
|
+
== Documentation
|
41
|
+
|
42
|
+
* {rdoc}[http://rdoc.info/gems/midi-jruby]
|
43
|
+
|
44
|
+
== Author
|
45
|
+
|
46
|
+
{Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
|
47
|
+
|
48
|
+
== License
|
49
|
+
|
50
|
+
Apache 2.0, See the file LICENSE
|
51
|
+
|
52
|
+
Copyright (c) 2011 Ari Russo
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module MIDIJRuby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Module containing methods used by both input and output devices
|
5
|
+
#
|
6
|
+
module Device
|
7
|
+
|
8
|
+
import javax.sound.midi.MidiSystem
|
9
|
+
import javax.sound.midi.MidiDevice
|
10
|
+
import javax.sound.midi.MidiEvent
|
11
|
+
import javax.sound.midi.Receiver
|
12
|
+
|
13
|
+
# has the device been initialized?
|
14
|
+
attr_reader :enabled,
|
15
|
+
# unique int id
|
16
|
+
:id,
|
17
|
+
# name property from javax.sound.midi.MidiDevice.Info
|
18
|
+
:name,
|
19
|
+
# description property from javax.sound.midi.MidiDevice.Info
|
20
|
+
:description,
|
21
|
+
# vendor property from javax.sound.midi.MidiDevice.Info
|
22
|
+
:vendor,
|
23
|
+
# :input or :output
|
24
|
+
:type
|
25
|
+
|
26
|
+
alias_method :enabled?, :enabled
|
27
|
+
|
28
|
+
def initialize(id, device, options = {}, &block)
|
29
|
+
@name = options[:name]
|
30
|
+
@description = options[:description]
|
31
|
+
@vendor = options[:vendor]
|
32
|
+
@id = id
|
33
|
+
@device = device
|
34
|
+
|
35
|
+
# cache the type name so that inspecting the class isn't necessary each time
|
36
|
+
@type = self.class.name.split('::').last.downcase.to_sym
|
37
|
+
|
38
|
+
@enabled = false
|
39
|
+
end
|
40
|
+
|
41
|
+
# select the first device of type <em>type</em>
|
42
|
+
def self.first(type)
|
43
|
+
all_by_type[type].first
|
44
|
+
end
|
45
|
+
|
46
|
+
# select the last device of type <em>type</em>
|
47
|
+
def self.last(type)
|
48
|
+
all_by_type[type].last
|
49
|
+
end
|
50
|
+
|
51
|
+
# a Hash of :input and :output devices
|
52
|
+
def self.all_by_type
|
53
|
+
available_devices = { :input => [], :output => [] }
|
54
|
+
count = -1
|
55
|
+
MidiSystem.get_midi_device_info.each do |info|
|
56
|
+
device = MidiSystem.get_midi_device(info)
|
57
|
+
opts = { :name => info.get_name,
|
58
|
+
:description => info.get_description,
|
59
|
+
:vendor => info.get_vendor }
|
60
|
+
available_devices[:output] << Output.new(count += 1, device, opts) unless device.get_max_receivers.zero?
|
61
|
+
available_devices[:input] << Input.new(count += 1, device, opts) unless device.get_max_transmitters.zero?
|
62
|
+
end
|
63
|
+
available_devices
|
64
|
+
end
|
65
|
+
|
66
|
+
# all devices of both types
|
67
|
+
def self.all
|
68
|
+
all_by_type.values.flatten
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module MIDIJRuby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Input device class
|
5
|
+
#
|
6
|
+
class Input
|
7
|
+
|
8
|
+
import javax.sound.midi.Transmitter
|
9
|
+
|
10
|
+
include Device
|
11
|
+
|
12
|
+
attr_reader :buffer
|
13
|
+
|
14
|
+
class InputReceiver
|
15
|
+
|
16
|
+
include javax.sound.midi.Receiver
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
attr_reader :stream
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@buf = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def read
|
26
|
+
to_return = @buf.dup
|
27
|
+
@buf.clear
|
28
|
+
to_return
|
29
|
+
end
|
30
|
+
|
31
|
+
def send(msg, timestamp = -1)
|
32
|
+
if msg.respond_to?(:get_packed_msg)
|
33
|
+
m = msg.get_packed_msg
|
34
|
+
@buf << unpack(m)
|
35
|
+
else
|
36
|
+
str = String.from_java_bytes(msg.get_data)
|
37
|
+
arr = str.unpack("C" * str.length)
|
38
|
+
arr.insert(0, msg.get_status)
|
39
|
+
@buf << arr
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def unpack(msg)
|
46
|
+
# there's probably a better way of doing this
|
47
|
+
o = []
|
48
|
+
s = msg.to_s(16)
|
49
|
+
s = "0" + s if s.length.divmod(2).last > 0
|
50
|
+
while s.length > 0
|
51
|
+
o << s.slice!(0,2).hex
|
52
|
+
end
|
53
|
+
o.reverse
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# returns an array of MIDI event hashes as such:
|
60
|
+
# [
|
61
|
+
# { :data => [144, 60, 100], :timestamp => 1024 },
|
62
|
+
# { :data => [128, 60, 100], :timestamp => 1100 },
|
63
|
+
# { :data => [144, 40, 120], :timestamp => 1200 }
|
64
|
+
# ]
|
65
|
+
#
|
66
|
+
# the data is an array of Numeric bytes
|
67
|
+
# the timestamp is the number of millis since this input was enabled
|
68
|
+
#
|
69
|
+
def gets
|
70
|
+
until queued_messages?
|
71
|
+
end
|
72
|
+
msgs = queued_messages
|
73
|
+
@pointer = @buffer.length
|
74
|
+
msgs
|
75
|
+
end
|
76
|
+
alias_method :read, :gets
|
77
|
+
|
78
|
+
# same as gets but returns message data as string of hex digits as such:
|
79
|
+
# [
|
80
|
+
# { :data => "904060", :timestamp => 904 },
|
81
|
+
# { :data => "804060", :timestamp => 1150 },
|
82
|
+
# { :data => "90447F", :timestamp => 1300 }
|
83
|
+
# ]
|
84
|
+
#
|
85
|
+
#
|
86
|
+
def gets_s
|
87
|
+
msgs = gets
|
88
|
+
msgs.each { |msg| msg[:data] = numeric_bytes_to_hex_string(msg[:data]) }
|
89
|
+
msgs
|
90
|
+
end
|
91
|
+
alias_method :gets_bytestr, :gets_s
|
92
|
+
alias_method :gets_hex, :gets_s
|
93
|
+
|
94
|
+
# enable this the input for use; can be passed a block
|
95
|
+
def enable(options = {}, &block)
|
96
|
+
@device.open
|
97
|
+
@transmitter = @device.get_transmitter
|
98
|
+
@transmitter.set_receiver(InputReceiver.new)
|
99
|
+
initialize_buffer
|
100
|
+
@start_time = Time.now.to_f
|
101
|
+
spawn_listener!
|
102
|
+
@enabled = true
|
103
|
+
if block_given?
|
104
|
+
begin
|
105
|
+
block.call(self)
|
106
|
+
ensure
|
107
|
+
close
|
108
|
+
end
|
109
|
+
else
|
110
|
+
self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
alias_method :open, :enable
|
114
|
+
alias_method :start, :enable
|
115
|
+
|
116
|
+
# close this input
|
117
|
+
def close
|
118
|
+
@listener.kill
|
119
|
+
@transmitter.close
|
120
|
+
@device.close
|
121
|
+
@enabled = false
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.first
|
125
|
+
Device.first(:input)
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.last
|
129
|
+
Device.last(:input)
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.all
|
133
|
+
Device.all_by_type[:input]
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def initialize_buffer
|
139
|
+
@buffer = []
|
140
|
+
@pointer = 0
|
141
|
+
def @buffer.clear
|
142
|
+
@pointer = 0
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def now
|
148
|
+
((Time.now.to_f - @start_time) * 1000)
|
149
|
+
end
|
150
|
+
|
151
|
+
# give a message its timestamp and package it in a Hash
|
152
|
+
def get_message_formatted(raw, time)
|
153
|
+
{ :data => raw, :timestamp => time }
|
154
|
+
end
|
155
|
+
|
156
|
+
def queued_messages
|
157
|
+
@buffer.slice(@pointer, @buffer.length - @pointer)
|
158
|
+
end
|
159
|
+
|
160
|
+
def queued_messages?
|
161
|
+
@pointer < @buffer.length
|
162
|
+
end
|
163
|
+
|
164
|
+
# launch a background thread that collects messages
|
165
|
+
def spawn_listener!
|
166
|
+
@listener = Thread.fork do
|
167
|
+
while true
|
168
|
+
while (msgs = poll_system_buffer).empty?
|
169
|
+
sleep(1.0/1000)
|
170
|
+
end
|
171
|
+
populate_local_buffer(msgs) unless msgs.empty?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def poll_system_buffer
|
177
|
+
@transmitter.get_receiver.read
|
178
|
+
end
|
179
|
+
|
180
|
+
def populate_local_buffer(msgs)
|
181
|
+
msgs.each { |raw| @buffer << get_message_formatted(raw, now) unless raw.nil? }
|
182
|
+
end
|
183
|
+
|
184
|
+
def numeric_bytes_to_hex_string(bytes)
|
185
|
+
bytes.map { |b| s = b.to_s(16).upcase; b < 16 ? s = "0" + s : s; s }.join
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module MIDIJRuby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Output device class
|
5
|
+
#
|
6
|
+
class Output
|
7
|
+
import javax.sound.midi.ShortMessage
|
8
|
+
import javax.sound.midi.SysexMessage
|
9
|
+
|
10
|
+
include Device
|
11
|
+
|
12
|
+
# close this output
|
13
|
+
def close
|
14
|
+
@device.close
|
15
|
+
@enabled = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# sends a MIDI message comprised of a String of hex digits
|
19
|
+
def puts_s(data)
|
20
|
+
data = data.dup
|
21
|
+
output = []
|
22
|
+
until (str = data.slice!(0,2)).eql?("")
|
23
|
+
output << str.hex
|
24
|
+
end
|
25
|
+
puts_bytes(*output)
|
26
|
+
end
|
27
|
+
alias_method :puts_bytestr, :puts_s
|
28
|
+
alias_method :puts_hex, :puts_s
|
29
|
+
|
30
|
+
# sends a MIDI messages comprised of Numeric bytes
|
31
|
+
def puts_bytes(*data)
|
32
|
+
if data.first.eql?(0xF0)
|
33
|
+
msg = SysexMessage.new
|
34
|
+
msg.set_message(data.to_java(:byte), data.length)
|
35
|
+
else
|
36
|
+
msg = ShortMessage.new
|
37
|
+
msg.set_message(*data)
|
38
|
+
end
|
39
|
+
@device.get_receiver.send(msg, 0)
|
40
|
+
end
|
41
|
+
|
42
|
+
# send a MIDI message of an indeterminant type
|
43
|
+
def puts(*a)
|
44
|
+
case a.first
|
45
|
+
when Array then puts_bytes(*a.first)
|
46
|
+
when Numeric then puts_bytes(*a)
|
47
|
+
when String then puts_bytestr(*a)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias_method :write, :puts
|
51
|
+
|
52
|
+
# enable this device; also takes a block
|
53
|
+
def enable(options = {}, &block)
|
54
|
+
@device.open
|
55
|
+
@enabled = true
|
56
|
+
unless block.nil?
|
57
|
+
begin
|
58
|
+
block.call(self)
|
59
|
+
ensure
|
60
|
+
close
|
61
|
+
end
|
62
|
+
else
|
63
|
+
self
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias_method :open, :enable
|
67
|
+
alias_method :start, :enable
|
68
|
+
|
69
|
+
def self.first
|
70
|
+
Device.first(:output)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.last
|
74
|
+
Device.last(:output)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.all
|
78
|
+
Device.all_by_type[:output]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
data/lib/midi-jruby.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Set of modules and classes for interacting with javax.sound.midi
|
6
|
+
#
|
7
|
+
module MIDIJRuby
|
8
|
+
|
9
|
+
VERSION = "0.0.12"
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'midi-jruby/device'
|
14
|
+
require 'midi-jruby/input'
|
15
|
+
require 'midi-jruby/output'
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: midi-jruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.12
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ari Russo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-03-04 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Simple MIDI wrapper for realtime IO in JRuby. Uses javax.sound.midi. Note that this library requires JRuby to be run in '1.9 mode'
|
18
|
+
email:
|
19
|
+
- ari.russo@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- lib/midi-jruby.rb
|
28
|
+
- lib/midi-jruby/device.rb
|
29
|
+
- lib/midi-jruby/input.rb
|
30
|
+
- lib/midi-jruby/output.rb
|
31
|
+
- LICENSE
|
32
|
+
- README.rdoc
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/arirusso/midi-jruby
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.3.6
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project: midi-jruby
|
57
|
+
rubygems_version: 1.5.1
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Simple MIDI wrapper for realtime IO in JRuby
|
61
|
+
test_files: []
|
62
|
+
|