jsound 0.1.0-java

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.
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,18 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A device that prints out all incoming MIDI message.
6
+ class Monitor < Device
7
+ def message(message)
8
+ source = message.source
9
+ if source and source.respond_to? :description
10
+ source = "#{source.description} => "
11
+ end
12
+ puts "#{source}#{message}"
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A device that sends all it's received messages to a system MIDI output port.
6
+ #
7
+ # Available outputs are contained in the {OUTPUTS} list in the {Midi} module.
8
+ #
9
+ class OutputDevice < JDevice
10
+
11
+ # Wrap a javax.sound.midi.MidiDevice transmitter to provide MIDI output.
12
+ #
13
+ # @note Typically you won't instantiate these directly. Instead, find an output via the {OUTPUTS} list in the {Midi} module.
14
+ #
15
+ def initialize(java_device)
16
+ super(java_device, :output)
17
+ end
18
+
19
+ def output= device
20
+ raise "#{self.class} cannot be assigned an output"
21
+ end
22
+
23
+ def message(message)
24
+ # unwrap the ruby message wrapper, if needed:
25
+ message = message.to_java if message.respond_to? :to_java
26
+
27
+ # Use java_send to call Receiver.send() since it conflicts with Ruby's built-in send method
28
+ # -1 means no timestamp, so we're not supporting timestamps
29
+ @java_device.receiver.java_send(:send, [javax.sound.midi.MidiMessage, Java::long], message, -1)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,56 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A Device that records incoming messages, and the timestamp at which they were received.
6
+ class Recorder < Device
7
+
8
+ # The recorded [message,timestamp] pairs
9
+ attr_reader :messages_with_timestamps
10
+
11
+ # The recorded messages without timestamps
12
+ def messages
13
+ @messages_with_timestamps.map{|m,t| m }
14
+ end
15
+
16
+ def initialize(autostart=true)
17
+ clear
18
+ if autostart
19
+ start
20
+ else
21
+ stop
22
+ end
23
+ end
24
+
25
+ # clear any recorded messages
26
+ def clear
27
+ @messages_with_timestamps = []
28
+ end
29
+
30
+ # start recording
31
+ def open
32
+ @recording = true
33
+ end
34
+ alias start open
35
+
36
+ # stop recording
37
+ def close
38
+ @recording = false
39
+ end
40
+ alias stop close
41
+
42
+ # true if this object is currently recording
43
+ def open?
44
+ @recording
45
+ end
46
+ alias recording? open?
47
+
48
+ def message(message)
49
+ @messages_with_timestamps << [message, Time.now.to_i] if recording?
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ # A device which repeats the input message to multiple outputs.
6
+ class Repeater < Device
7
+
8
+ # connect device(s) as the outputs for this device
9
+ # @param [Enumberable, Device] the device or devices to connect, or nil to disconnect the currently connected device
10
+ # @see {#>>}
11
+ def output= device
12
+ device = [device] if not device.is_a? Enumerable
13
+ super
14
+ end
15
+
16
+ def message(message)
17
+ if @output
18
+ for device in @output
19
+ device.message(message)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module JSound
2
+ module Midi
3
+ module Devices
4
+
5
+ class Transformer < Device
6
+
7
+ # The transformation block: a lambda that takes a message and returns
8
+ # either a transformed message or an Enumerable list of messages
9
+ attr_accessor :message_processor
10
+
11
+ def initialize(&message_processor)
12
+ @message_processor = message_processor
13
+ end
14
+
15
+ def message(message)
16
+ if @output and @message_processor
17
+ transformed_message = @message_processor.call(message)
18
+ if transformed_message.is_a? Enumerable
19
+ transformed_message.each{|m| @output.message(m) }
20
+ else
21
+ @output.message(transformed_message) if transformed_message
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,165 @@
1
+ module JSound
2
+ module Midi
3
+
4
+ # A generic MIDI message.
5
+ # The various subclasses in this module deal with specific message details.
6
+ # See http://www.midi.org/techspecs/midimessages.php
7
+ # for info on how the MIDI spec defines these messages.
8
+ class Message
9
+ include JSound::Mixins::TypeFromClassName
10
+
11
+ # The MIDI input {Device} which received this message.
12
+ attr_reader :source
13
+
14
+ # The variable data for this message type. Contents depend on the message type.
15
+ # @example a NoteOn's #data is [pitch,velocity]
16
+ attr_reader :data
17
+
18
+ # The channel number of the message
19
+ attr_reader :channel
20
+
21
+ # The type of message, such as :note_on or :control_change
22
+ # @return [Symbol]
23
+ attr_reader :type
24
+
25
+ def initialize(data, channel=0, options={})
26
+ @data = data
27
+ @channel = channel
28
+
29
+ # Generic Message objects specify a type explicitly (see initialize).
30
+ # Subclasses will typically use the class type (see JSound::Mixins::TypeFromClassName).
31
+ @type = options[:type] ||= self.class.type
32
+
33
+ @java_message = options[:java_message]
34
+
35
+ @source = options[:source]
36
+ end
37
+
38
+ # Map java message status values to ruby classes
39
+ CLASS_FOR_STATUS = {}
40
+
41
+ # Map ruby classes to java message status values
42
+ STATUS_FOR_CLASS = {}
43
+
44
+ def self.inherited(child_class)
45
+ # I'm using the convention that the message class names
46
+ # correspond to the java ShortMessage constants, like:
47
+ # NoteOn => ShortMessage::NOTE_ON
48
+ const_name = child_class.type.to_s.upcase
49
+ if javax.sound.midi.ShortMessage.const_defined? const_name
50
+ status = javax.sound.midi.ShortMessage.const_get(const_name)
51
+ CLASS_FOR_STATUS[status] = child_class
52
+ STATUS_FOR_CLASS[child_class] = status
53
+ end
54
+ end
55
+
56
+ TYPE_FOR_STATUS = {
57
+ javax.sound.midi.ShortMessage::ACTIVE_SENSING => :active_sensing,
58
+ javax.sound.midi.ShortMessage::CONTINUE => :continue,
59
+ javax.sound.midi.ShortMessage::END_OF_EXCLUSIVE => :end_of_exclusive,
60
+ javax.sound.midi.ShortMessage::MIDI_TIME_CODE => :multi_time_code,
61
+ javax.sound.midi.ShortMessage::SONG_POSITION_POINTER => :song_position_pointer,
62
+ javax.sound.midi.ShortMessage::SONG_SELECT => :song_select,
63
+ javax.sound.midi.ShortMessage::START => :start,
64
+ javax.sound.midi.ShortMessage::STOP => :stop,
65
+ javax.sound.midi.ShortMessage::SYSTEM_RESET => :system_reset,
66
+ javax.sound.midi.ShortMessage::TIMING_CLOCK => :timing_clock,
67
+ javax.sound.midi.ShortMessage::TUNE_REQUEST => :tune_request
68
+ }
69
+
70
+ STATUS_FOR_TYPE = TYPE_FOR_STATUS.invert
71
+
72
+ # true when the argument has the same {#type}, {#channel}, and {#data}
73
+ def == other
74
+ other.respond_to? :type and type == other.type and
75
+ other.respond_to? :channel and channel == other.channel and
76
+ other.respond_to? :data and data == other.data
77
+ end
78
+
79
+ def self.from_java(java_message, options={})
80
+ case java_message
81
+ when javax.sound.midi.SysexMessage
82
+ type = :sysex
83
+ data = java_message.data # this is a byte array in Java, might need conversion?
84
+
85
+ when javax.sound.midi.MetaMessage
86
+ type = :meta
87
+ data = java_message.data # this is a byte array in Java, might need conversion?
88
+
89
+ when javax.sound.midi.ShortMessage
90
+ # For command-type messages, the least significant 4 bits of the status byte will be the channel number.
91
+ # java_message.command will return the desired command's status code in this case, or
92
+ # we can just use a bitmask to grab the most significant 4 bits of the status byte like so:
93
+ status = (java_message.status & 0xF0)
94
+
95
+ message_class = CLASS_FOR_STATUS[status]
96
+ return message_class.from_java(java_message, options) if message_class
97
+
98
+ type = TYPE_FOR_STATUS[status] || :unknown
99
+ data = [java_message.data1, java_message.data2]
100
+
101
+ else
102
+ type = :unknown
103
+ data = []
104
+ end
105
+
106
+ new data, java_message.channel, options.merge({:type => type, :java_message => java_message})
107
+ end
108
+
109
+ def to_java
110
+ if not @java_message
111
+ @java_message = javax.sound.midi.ShortMessage.new
112
+ update_java_message
113
+ end
114
+ # else, since all ruby message classes are backed by "ShortMessage",
115
+ # we should be able to rely on @java_message being set for everything else
116
+ @java_message
117
+ end
118
+
119
+ def update_java_message
120
+ @java_message.setMessage(status, @channel, @data[0], @data[1]) if @java_message
121
+ end
122
+
123
+ def data= data
124
+ @data = data
125
+ update_java_message
126
+ end
127
+
128
+ def data1
129
+ @data[0] if @data
130
+ end
131
+
132
+ def data1= data
133
+ @data[0] = data
134
+ update_java_message
135
+ end
136
+
137
+ def data2
138
+ @data[1] if @data
139
+ end
140
+
141
+ def data2= data
142
+ @data[1] = data
143
+ update_java_message
144
+ end
145
+
146
+ def status
147
+ @status ||= (STATUS_FOR_CLASS[self.class] || STATUS_FOR_TYPE[@type])
148
+ end
149
+
150
+ def value
151
+ @data
152
+ end
153
+
154
+ def to_s
155
+ "#{type}(#{channel}): #{value.inspect}"
156
+ end
157
+
158
+ def clone
159
+ self.class.new(@data, @channel, {:type => @type})
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,52 @@
1
+ module JSound
2
+ module Midi
3
+
4
+ # A collection of methods for building MIDI messages.
5
+ module MessageBuilder
6
+
7
+ def note_on(pitch, velocity=127, channel=0)
8
+ Messages::NoteOn.new(pitch,velocity,channel)
9
+ end
10
+
11
+ def note_off(pitch, velocity=127, channel=0)
12
+ Messages::NoteOff.new(pitch,velocity,channel)
13
+ end
14
+
15
+ # Most methods in here take 7-bit ints for their args, but this one takes a 14-bit
16
+ # The value can be an int in the range 0-16383 (8192 is no bend)
17
+ # or it can be a float, which is assumed to be in the range -1.0 to 1.0
18
+ def pitch_bend(value, channel=0)
19
+ Messages::PitchBend.new(value, channel)
20
+ end
21
+
22
+ def control_change(control, value, channel=0)
23
+ Messages::ControlChange.new(control, value, channel)
24
+ end
25
+
26
+ def all_notes_off(channel=0)
27
+ control_change(123, 0, channel)
28
+ end
29
+
30
+ def channel_pressure(pressure, channel=0)
31
+ Messages::ChannelPressure.new(pressure, channel)
32
+ end
33
+ alias channel_aftertouch channel_pressure
34
+
35
+ def poly_pressure(pitch, pressure, channel=0)
36
+ Messages::PolyPressure.new(pitch, pressure, channel)
37
+ end
38
+ alias poly_aftertouch poly_pressure
39
+
40
+ def program_change(program, channel=0)
41
+ Messages::ProgramChange.new(program, channel)
42
+ end
43
+
44
+ # Make all methods be module functions (accessible by sending the method name to module directly)
45
+ instance_methods.each do |method|
46
+ module_function method
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ module JSound
2
+ module Midi
3
+ module Messages
4
+
5
+ class ChannelPressure < Message
6
+
7
+ def initialize(pressure, channel=0, options={})
8
+ super([pressure, 0], channel, options)
9
+ end
10
+
11
+ alias pressure data1
12
+ alias pressure= data1=
13
+
14
+ def self.from_java(java_message, options={})
15
+ new java_message.data1, java_message.channel, options.merge({:java_message => java_message})
16
+ end
17
+
18
+ def clone
19
+ self.class.new(pressure,@channel)
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module JSound
2
+ module Midi
3
+ module Messages
4
+
5
+ class ControlChange < Message
6
+
7
+ def initialize(control, value, channel=0, options={})
8
+ super([control,value], channel, options)
9
+ end
10
+
11
+ alias control data1
12
+ alias control= data1=
13
+
14
+ alias value data2
15
+ alias value= data2=
16
+
17
+ def self.from_java(java_message, options={})
18
+ new java_message.data1, java_message.data2, java_message.channel, options.merge({:java_message => java_message})
19
+ end
20
+
21
+ def clone
22
+ self.class.new(control,value,@channel)
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end