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,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