jsound 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.yardopts +3 -0
  2. data/LICENSE.txt +27 -0
  3. data/README.md +96 -0
  4. data/Rakefile +19 -0
  5. data/examples/harmonizer.rb +25 -0
  6. data/examples/launchpad/launchpad.rb +65 -0
  7. data/examples/launchpad/launchpad_generator.rb +111 -0
  8. data/examples/list_devices.rb +6 -0
  9. data/examples/monitor.rb +15 -0
  10. data/examples/notes.rb +90 -0
  11. data/examples/transposer.rb +20 -0
  12. data/lib/jsound.rb +4 -0
  13. data/lib/jsound/convert.rb +30 -0
  14. data/lib/jsound/midi.rb +77 -0
  15. data/lib/jsound/midi/device.rb +72 -0
  16. data/lib/jsound/midi/device_list.rb +157 -0
  17. data/lib/jsound/midi/devices/generator.rb +22 -0
  18. data/lib/jsound/midi/devices/input_device.rb +51 -0
  19. data/lib/jsound/midi/devices/jdevice.rb +100 -0
  20. data/lib/jsound/midi/devices/monitor.rb +18 -0
  21. data/lib/jsound/midi/devices/output_device.rb +36 -0
  22. data/lib/jsound/midi/devices/recorder.rb +56 -0
  23. data/lib/jsound/midi/devices/repeater.rb +28 -0
  24. data/lib/jsound/midi/devices/transformer.rb +30 -0
  25. data/lib/jsound/midi/message.rb +165 -0
  26. data/lib/jsound/midi/message_builder.rb +52 -0
  27. data/lib/jsound/midi/messages/channel_pressure.rb +26 -0
  28. data/lib/jsound/midi/messages/control_change.rb +29 -0
  29. data/lib/jsound/midi/messages/note_off.rb +10 -0
  30. data/lib/jsound/midi/messages/note_on.rb +29 -0
  31. data/lib/jsound/midi/messages/pitch_bend.rb +43 -0
  32. data/lib/jsound/midi/messages/poly_pressure.rb +29 -0
  33. data/lib/jsound/midi/messages/program_change.rb +27 -0
  34. data/lib/jsound/type_from_class_name.rb +23 -0
  35. data/spec/jsound/convert_spec.rb +68 -0
  36. data/spec/jsound/midi/device_spec.rb +75 -0
  37. data/spec/jsound/midi/devices/generator_spec.rb +21 -0
  38. data/spec/jsound/midi/devices/output_device_spec.rb +22 -0
  39. data/spec/jsound/midi/devices/recorder_spec.rb +88 -0
  40. data/spec/jsound/midi/devices/repeater_device_spec.rb +19 -0
  41. data/spec/jsound/midi/devices/transformer_spec.rb +20 -0
  42. data/spec/jsound/midi/devlice_list_spec.rb +60 -0
  43. data/spec/jsound/midi/message_builder_spec.rb +22 -0
  44. data/spec/jsound/midi/message_spec.rb +30 -0
  45. data/spec/jsound/midi/messages/note_off_spec.rb +62 -0
  46. data/spec/jsound/midi/messages/note_on_spec.rb +109 -0
  47. data/spec/jsound/midi/messages/pitch_bend_spec.rb +88 -0
  48. data/spec/jsound/midi_spec.rb +33 -0
  49. data/spec/jsound/type_from_class_name_spec.rb +26 -0
  50. data/spec/spec_helper.rb +23 -0
  51. metadata +103 -0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env jruby
2
+ require 'rubygems'
3
+ require 'jsound'
4
+ include JSound::Midi
5
+ include Devices
6
+
7
+ transposer = Transformer.new do |message|
8
+ message.pitch += 24 if message.respond_to? :pitch # transpose up two octaves
9
+ message
10
+ end
11
+
12
+ # Adjust the INPUTS and OUTPUTS as needed to use the devices you want:
13
+ INPUTS.open_first >> transposer >> OUTPUTS.open_first
14
+ # For example, to send my Akai keyboard through the harmonizer to the OS X IAC bus, I can do:
15
+ # INPUTS.Akai >> transposer >> OUTPUTS.IAC
16
+
17
+ # force the script to keep running (MIDI devices run in a background thread)
18
+ while(true)
19
+ sleep 5
20
+ end
@@ -0,0 +1,4 @@
1
+ require 'java'
2
+ require 'jsound/convert'
3
+ require 'jsound/type_from_class_name'
4
+ require 'jsound/midi'
@@ -0,0 +1,30 @@
1
+ module JSound
2
+
3
+ # Helper methods for converting MIDI data values
4
+ module Convert
5
+
6
+ # The maximum value for unsigned 14-bit integer
7
+ MAX_14BIT_VALUE = 16383 # == 127 + (127 << 7)
8
+
9
+ # Convert a single integer to a [least_significant, most_significant] pair of 7-bit ints
10
+ def to_7bit(value)
11
+ [value & 127, (value >> 7) & 127]
12
+ end
13
+
14
+ # Convert a [least_significant, most_significant] pair of 7-bit ints to a single integer
15
+ def from_7bit(lsb, msb)
16
+ lsb + (msb << 7)
17
+ end
18
+
19
+ # Scales a float from the range [-1.0, 1.0] to the integer range [0, 16383]
20
+ def normalized_float_to_14bit(float)
21
+ (MAX_14BIT_VALUE*(float+1)/2).round
22
+ end
23
+
24
+ # Make all methods be module functions (accessible by sending the method name to module directly)
25
+ instance_methods.each do |method|
26
+ module_function method
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,77 @@
1
+ require 'jsound/midi/message'
2
+ require 'jsound/midi/messages/channel_pressure'
3
+ require 'jsound/midi/messages/control_change'
4
+ require 'jsound/midi/messages/note_on'
5
+ require 'jsound/midi/messages/note_off'
6
+ require 'jsound/midi/messages/pitch_bend'
7
+ require 'jsound/midi/messages/poly_pressure'
8
+ require 'jsound/midi/messages/program_change'
9
+
10
+ require 'jsound/midi/message_builder'
11
+
12
+ require 'jsound/midi/device'
13
+ require 'jsound/midi/devices/generator'
14
+ require 'jsound/midi/devices/jdevice'
15
+ require 'jsound/midi/devices/input_device'
16
+ require 'jsound/midi/devices/output_device'
17
+ require 'jsound/midi/devices/monitor'
18
+ require 'jsound/midi/devices/recorder'
19
+ require 'jsound/midi/devices/repeater'
20
+ require 'jsound/midi/devices/transformer'
21
+
22
+ require 'jsound/midi/device_list'
23
+
24
+
25
+ module JSound
26
+
27
+ # Module containing all MIDI functionality.
28
+ #
29
+ # Also provies the core interface for accessing MIDI devices, see the device list constants defined here.
30
+ #
31
+ module Midi
32
+ include_package 'javax.sound.midi'
33
+
34
+ # All MIDI devices
35
+ # @return [DeviceList]
36
+ DEVICES = DeviceList.new
37
+
38
+ # MIDI input devices
39
+ # @return [DeviceList] a list of {Devices::InputDevice}s
40
+ INPUTS = DeviceList.new
41
+
42
+ # MIDI output devices
43
+ # @return [DeviceList] a list of {Devices::OutputDevice}s
44
+ OUTPUTS = DeviceList.new
45
+
46
+ # MIDI synthesizer devices
47
+ # @return [DeviceList]
48
+ SYNTHESIZERS = SYNTHS = DeviceList.new
49
+
50
+ # MIDI sequencer devices
51
+ # @return [DeviceList]
52
+ SEQUENCERS = DeviceList.new
53
+
54
+ # Refresh the list of connected devices.
55
+ # @note this happens automatically when JSound is required.
56
+ def refresh_devices
57
+ [DEVICES,INPUTS,OUTPUTS,SYNTHESIZERS,SEQUENCERS].each{|collection| collection.clear}
58
+ MidiSystem.getMidiDeviceInfo.each do |device_info|
59
+ java_device = MidiSystem.getMidiDevice(device_info)
60
+ device = Devices::JDevice.from_java(java_device)
61
+ case device.type
62
+ when :sequencer then SEQUENCERS << device
63
+ when :synthesizer then SYNTHESIZERS << device
64
+ when :input then INPUTS << device
65
+ when :output then OUTPUTS << device
66
+ end
67
+ DEVICES << device
68
+ end
69
+ DEVICES
70
+ end
71
+ module_function :refresh_devices
72
+
73
+ # Refresh devices automatically when loaded:
74
+ refresh_devices()
75
+
76
+ end
77
+ end
@@ -0,0 +1,72 @@
1
+ module JSound
2
+ module Midi
3
+
4
+ # A device that can transmit and/or receive messages (typically MIDI messages).
5
+ # This default implementation simply passes through all messages.
6
+ class Device
7
+ include JSound::Mixins::TypeFromClassName
8
+
9
+ # Open the device and allocate the needed resources so that it can send and receive messages
10
+ # @note this operation is typically only relevant for Java-based devices such as {Devices::InputDevice} and {Devices::OutputDevice}
11
+ # @see DeviceList#open
12
+ def open
13
+ end
14
+
15
+ # return true if this device is currently open
16
+ def open?
17
+ # typically, ruby devices are always open, subclasses might not be
18
+ true
19
+ end
20
+
21
+ # Close the device and free up any resources used by this device.
22
+ # @note this operation is typically only relevant for Java-based devices such as {Devices::InputDevice} and {Devices::OutputDevice}
23
+ def close
24
+ end
25
+
26
+ def type
27
+ # The base Device behaves like a 'pass through'
28
+ @type ||= (self.class == Device ? :pass_through : self.class.type)
29
+ end
30
+
31
+ # the device connected to this device's output
32
+ # @return [Device] the connected device, or nil if nothing is connected
33
+ def output
34
+ @output
35
+ end
36
+
37
+ # connect a device as the output for this device
38
+ # @param [Device] the device to connect, or nil to disconnect the currently connected device
39
+ # @see {#>>}
40
+ def output= device
41
+ @output = device
42
+ end
43
+
44
+ # Connect a device as the output for this device. shortcut for {#output=}
45
+ # @param [Device] the device to connect, or nil to disconnect the currently connected device
46
+ # @see {#output=}
47
+ def >> device
48
+ self.output= device
49
+ end
50
+
51
+ # Send a message to this device
52
+ # @param [Message]
53
+ # @see #<=
54
+ def message(message)
55
+ # default behavior is to pass the message to any connected output
56
+ @output.message(message) if @output
57
+ end
58
+
59
+ # send a message to this device. shortcut for {#message}
60
+ # @see #message
61
+ def <=(message)
62
+ message(message)
63
+ end
64
+
65
+ def to_s
66
+ "MIDI #{type} device"
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,157 @@
1
+ module JSound
2
+ module Midi
3
+
4
+ # A collection of MIDI {Device}s that provides various methods to lookup (and optionally auto-open) specific devices
5
+ #
6
+ # @see Midi the Midi module for constants to access your system devices
7
+ #
8
+ class DeviceList
9
+
10
+ # The devices within this collection.
11
+ attr_reader :devices
12
+
13
+ def initialize(list=[])
14
+ @devices = list
15
+ end
16
+
17
+ # Find the first {Device} matching the criteria
18
+ #
19
+ # @param criteria matches description against for a Regexp argument, matches the device field against the value for a Hash argument, otherwise checks for an equal description
20
+ # @return [Device] the first matching {Device} or nil if nothing matched
21
+ # @note I/O devices need to be opened before they can be used (see {Device#open})
22
+ # @see #find_all
23
+ # @see #open
24
+ # @example {OUTPUTS}.find "IAC Driver Bus 1" #=> find the first output with the exact description "IAC Driver Bus 1"
25
+ # @example {OUTPUTS}.find /IAC/ #=> find the first output with a description containing "IAC"
26
+ # @example {OUTPUTS}.find :vendor => "Apple Inc." #=> find the first output with the exact vendor field "Apple Inc."
27
+ # @example {OUTPUTS}.find :vendor => /Apple / #=> find the first output with a vendor field containing "Apple"
28
+ #
29
+ def find(criteria)
30
+ search :find, criteria
31
+ end
32
+
33
+ # Find all {Device}s matching the criteria
34
+ #
35
+ # @param criteria matches description against for a Regexp argument, matches the device field against the value for a Hash argument, otherwise checks for an equal description
36
+ # @return [Array] an Array of all matching {Device}s or [] if nothing matched
37
+ # @note I/O devices need to be opened before they can be used (see {Device#open})
38
+ # @see #find for examples
39
+ # @see #open
40
+ #
41
+ def find_all(criteria)
42
+ search :find_all, criteria
43
+ end
44
+
45
+ # Acts like Array#[] for Numeric and Range arguments. Otherwise acts like {#find_all}.
46
+ #
47
+ # @see #find_all
48
+ #
49
+ def [](criteria)
50
+ if criteria.kind_of? Fixnum or criteria.kind_of? Range
51
+ @devices[criteria]
52
+ else
53
+ find_all(criteria)
54
+ end
55
+ end
56
+
57
+ # Find the first device matching the argument and open it.
58
+ #
59
+ # @param device matches description against for a Regexp argument, matches the device field against the value for a Hash argument, otherwise checks for an equal description
60
+ # @return [Device] the device
61
+ # @raise an error if no device can be found
62
+ # @see #find
63
+ # @see #find_all
64
+ #
65
+ def open(device)
66
+ device = find device unless device.is_a? Device
67
+ if device
68
+ device.open
69
+ else
70
+ raise "Device note found: #{device}"
71
+ end
72
+ device
73
+ end
74
+
75
+ # Find and open the first device with a description matching the argument.
76
+ #
77
+ # @return [Device] the device
78
+ # @raise an error if no device can be found
79
+ # @see #open
80
+ #
81
+ def /(regexp)
82
+ regexp = Regexp.new(regexp.to_s, Regexp::IGNORECASE) if not regexp.kind_of? Regexp
83
+ open regexp
84
+ end
85
+
86
+ # open the first device in this list
87
+ #
88
+ # @return [Device] the device
89
+ # @raise an error if no device can be found
90
+ # @see #open
91
+ #
92
+ def open_first
93
+ open @devices.first
94
+ end
95
+
96
+ # open and return the last device in this list
97
+ #
98
+ # @return [Device] the device
99
+ # @raise an error if no device can be found
100
+ # @see #open
101
+ #
102
+ def open_last
103
+ open @devices.first
104
+ end
105
+
106
+ def to_s
107
+ @devices.join("\n")
108
+ end
109
+
110
+ ########################
111
+ private
112
+
113
+ def search(iterator, criteria)
114
+ field, target_value = field_and_target_value_for(criteria)
115
+ matcher = matcher_for(target_value)
116
+ @devices.send(iterator) do |device|
117
+ device.send(field).send(matcher, target_value)
118
+ end
119
+ end
120
+
121
+ def field_and_target_value_for(criteria)
122
+ if criteria.is_a? Hash
123
+ first_key = criteria.keys.first
124
+ return first_key, criteria[first_key]
125
+ else
126
+ return :description, criteria
127
+ end
128
+ end
129
+
130
+ def matcher_for(target_value)
131
+ case target_value
132
+ when Regexp then '=~'
133
+ else '=='
134
+ end
135
+ end
136
+
137
+ # Pass method calls through to the underlying Array.
138
+ # If Array doesn't understand the method, call {#open} with a case-insensitive regexp match
139
+ # against description, treating underscore as a wildcard.
140
+ #
141
+ # @example # {INPUTS}.Akai => open the first "Akai" input device connected to this computer
142
+ # @example # {INPUTS}.Akai_2 => open an input device matching /Akai.*2/i
143
+ #
144
+ def method_missing(sym, *args, &block)
145
+ if @devices.respond_to? sym
146
+ @devices.send(sym, *args, &block)
147
+ elsif args.length == 0 and block.nil?
148
+ self.open /#{sym.to_s.sub '_','.*'}/i
149
+ else
150
+ super
151
+ end
152
+ end
153
+
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,22 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A device which provides methods for generating MIDI command messages.
6
+ # See JSound::Midi::Messages::Builder for the available methods.
7
+ class Generator < Device
8
+
9
+ # For all the methods defined in the MessageBuilder module (note_on, pitch_bend, control_change, etc),
10
+ # define a corresponding device method that constructs the message and sends the connected device
11
+ MessageBuilder.private_instance_methods.each do |method|
12
+ define_method(method) do |*args|
13
+ message = MessageBuilder.send(method, *args)
14
+ self.message message
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A device that receives messages from a system MIDI input port,
6
+ # and passes the messages to any connected device.
7
+ #
8
+ # Available inputs are contained in the {INPUTS} list in the {Midi} module.
9
+ #
10
+ class InputDevice < JDevice
11
+
12
+ # Wrap a javax.sound.midi.MidiDevice receiver to provide MIDI output.
13
+ #
14
+ # @note Typically you won't instantiate these directly. Instead, find an input via the {INPUTS} list in the {Midi} module.
15
+ #
16
+ def initialize(java_device)
17
+ super(java_device, :input)
18
+ @bridge = Bridge.new(self)
19
+ java_device.transmitter.receiver = @bridge
20
+ end
21
+
22
+ def output=(device)
23
+ super
24
+ @bridge.output= device
25
+ end
26
+
27
+ # A subcomponent of {InputDevice} that implements javax.sound.midi.Receiver
28
+ # by translating incoming Java MIDI Messages to Ruby Messages.
29
+ class Bridge < Device
30
+ include javax.sound.midi.Receiver
31
+
32
+ def initialize(source_device)
33
+ @source_device = source_device
34
+ end
35
+
36
+ # Receives incoming Java MIDI Messages, converts them to Ruby Messages,
37
+ # and sends them to any connected devices.
38
+ def send(java_message, timestamp=-1)
39
+ self.message Message.from_java(java_message, :source => @source_device)
40
+ rescue
41
+ STDERR.puts $! if $DEBUG # otherwise this can get swallowed
42
+ raise
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,100 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A Java-provided MIDI device (wraps javax.sound.midi.MidiDevice objects)
6
+ class JDevice < Device
7
+
8
+ # the javax.sound.midi.MidiDevice.Info object for this java device
9
+ attr_reader :info
10
+
11
+ # the description of this device
12
+ # @return [String]
13
+ attr_reader :description
14
+
15
+ attr_reader :type
16
+
17
+ # All open JDevices
18
+ # @note All open devices will be automatically closed at_exit
19
+ def self.open_devices
20
+ @@open_devices ||= []
21
+ end
22
+ at_exit do
23
+ # Close all open devices so we don't hang the program at shutdown time
24
+ for device in JDevice.open_devices
25
+ device.close
26
+ end
27
+ end
28
+
29
+ def initialize(java_device, type)
30
+ @java_device = java_device
31
+ @info = @java_device.deviceInfo
32
+ @description = @info.description
33
+ @type = type
34
+ end
35
+
36
+ def self.from_java(java_device)
37
+ case java_device
38
+ when javax.sound.midi.Sequencer then type = :sequencer
39
+ when javax.sound.midi.Synthesizer then type = :synthesizer
40
+ else
41
+ # This assumes a single device cannot be both an input and an output:
42
+ if java_device.maxTransmitters != 0
43
+ return InputDevice.new(java_device)
44
+ elsif java_device.maxReceivers != 0
45
+ return OutputDevice.new(java_device)
46
+ else
47
+ type = :unknown
48
+ end
49
+ end
50
+ new java_device, type
51
+ end
52
+
53
+ # @see Device#open
54
+ # @note All open devices will be automatically closed at_exit
55
+ def open
56
+ unless @java_device.open?
57
+ puts "Opening #{to_s}"
58
+ @java_device.open
59
+ self.class.open_devices << self
60
+ end
61
+ end
62
+
63
+ # @see Device#close
64
+ def close
65
+ if @java_device.open?
66
+ puts "Closing #{to_s}"
67
+ @java_device.close
68
+ self.class.open_devices.delete(self)
69
+ end
70
+ end
71
+
72
+ def method_missing(sym, *args, &block)
73
+ if @java_device.respond_to? sym
74
+ @java_device.send(sym, *args, &block)
75
+ else
76
+ @info.send(sym, *args, &block)
77
+ end
78
+ end
79
+
80
+ def respond_to?(sym)
81
+ super or @java_device.respond_to? sym or info.respond_to? sym
82
+ end
83
+
84
+ def [](field)
85
+ send field
86
+ end
87
+
88
+ def to_s
89
+ "#{super}: #{info.description}"
90
+ end
91
+
92
+ def inspect
93
+ to_s
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
100
+ end