midi-jruby 0.0.12

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