midi-message 0.4.8 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,46 +1,89 @@
1
1
  module MIDIMessage
2
2
 
3
- # Very simple parsing
4
- # for more advanced parsing check out {nibbler}[http://github.com/arirusso/nibbler]
3
+ # Simple message parsing
4
+ # For more advanced parsing check out {nibbler}[http://github.com/arirusso/nibbler]
5
5
  class Parser
6
6
 
7
+ MESSAGE_TYPE = [
8
+ NoteOff,
9
+ NoteOn,
10
+ PolyphonicAftertouch,
11
+ ControlChange,
12
+ ProgramChange,
13
+ ChannelAftertouch,
14
+ PitchBend,
15
+ SystemMessage
16
+ ].freeze
17
+
18
+ SYSTEM_MESSAGE_TYPE = [
19
+ SystemExclusive,
20
+ SystemCommon,
21
+ SystemRealtime
22
+ ].freeze
23
+
24
+ # Can take either a hex string eg Parser.new("904040")
25
+ # or bytes eg Parser.new(0x90, 0x40, 0x40)
26
+ # or an array of bytes eg Parser.new([0x90, 0x40, 0x40])
27
+ # @param [Array<Fixnum>, *Fixnum, String] args
28
+ # @return [MIDIMessage]
29
+ def self.parse(*args)
30
+ parser = new(*args)
31
+ parser.parse
32
+ end
33
+
7
34
  # Can take either a hex string eg Parser.new("904040")
8
35
  # or bytes eg Parser.new(0x90, 0x40, 0x40)
9
36
  # or an array of bytes eg Parser.new([0x90, 0x40, 0x40])
37
+ # @param [Array<Fixnum>, *Fixnum, String] args
38
+ # @return [MIDIMessage]
10
39
  def initialize(*args)
11
40
  @data = case args.first
12
41
  when Array then args.first
13
- when Numeric then args
42
+ when Numeric then args
14
43
  when String then TypeConversion.hex_string_to_numeric_byte_array(args.first)
15
44
  end
16
45
  end
17
46
 
18
47
  # Parse the data and return a message
48
+ # @return [MIDIMessage]
19
49
  def parse
20
- first_nibble = ((@data.first & 0xF0) >> 4)
21
- second_nibble = (@data.first & 0x0F)
22
- case first_nibble
23
- when 0x8 then NoteOff.new(second_nibble, @data[1], @data[2])
24
- when 0x9 then NoteOn.new(second_nibble, @data[1], @data[2])
25
- when 0xA then PolyphonicAftertouch.new(second_nibble, @data[1], @data[2])
26
- when 0xB then ControlChange.new(second_nibble, @data[1], @data[2])
27
- when 0xC then ProgramChange.new(second_nibble, @data[1])
28
- when 0xD then ChannelAftertouch.new(second_nibble, @data[1])
29
- when 0xE then PitchBend.new(second_nibble, @data[1], @data[2])
30
- when 0xF then case second_nibble
31
- when 0x0 then SystemExclusive.new(*@data)
32
- when 0x1..0x6 then SystemCommon.new(second_nibble, @data[1], @data[2])
33
- when 0x8..0xF then SystemRealtime.new(second_nibble)
34
- else nil
35
- end
36
- else nil
50
+ nibbles = get_nibbles
51
+ klass = MESSAGE_TYPE.find { |type| type::STATUS == nibbles[0] }
52
+ if klass == SystemMessage
53
+ build_system_message(nibbles[1])
54
+ else
55
+ klass.new(nibbles[1], *@data.drop(1))
37
56
  end
38
- end
57
+ end
58
+
59
+ private
60
+
61
+ def get_nibbles
62
+ [
63
+ ((@data.first & 0xF0) >> 4),
64
+ (@data.first & 0x0F)
65
+ ]
66
+ end
67
+
68
+ def build_system_message(id)
69
+ klass = SYSTEM_MESSAGE_TYPE.find do |type|
70
+ id == type::ID ||
71
+ (type::ID.kind_of?(Range) && type::ID.include?(id))
72
+ end
73
+ if klass == SystemExclusive
74
+ SystemExclusive.new(*@data)
75
+ else
76
+ klass.new(id, *@data.drop(1))
77
+ end
78
+ end
39
79
 
40
80
  end
41
81
 
82
+ # Shortcut to Parser.parse
83
+ # @param [Array<Fixnum>, *Fixnum, String] args
84
+ # @return [MIDIMessage]
42
85
  def self.parse(*args)
43
- Parser.new(*args).parse
86
+ Parser.parse(*args)
44
87
  end
45
88
 
46
89
  end
@@ -4,7 +4,7 @@ module MIDIMessage
4
4
  module SystemExclusive
5
5
 
6
6
  include MIDIMessage # this enables ..kind_of?(MIDIMessage)
7
-
7
+
8
8
  def self.included(base)
9
9
  base.send(:include, InstanceMethods)
10
10
  end
@@ -15,9 +15,6 @@ module MIDIMessage
15
15
  attr_accessor :node
16
16
  attr_reader :address, :checksum
17
17
 
18
- StartByte = 0xF0
19
- EndByte = 0xF7
20
-
21
18
  # an array of message parts. multiple byte parts will be represented as an array of bytes
22
19
  def to_a(options = {})
23
20
  omit = options[:omit] || []
@@ -56,20 +53,20 @@ module MIDIMessage
56
53
  alias_method :to_bytestr, :to_hex_s
57
54
 
58
55
  def name
59
- "System Exclusive"
56
+ SystemExclusive::DISPLAY_NAME
60
57
  end
61
58
  alias_method :verbose_name, :name
62
59
 
63
60
  def start_byte
64
- self.class::StartByte
61
+ SystemExclusive::DELIMITER[:start]
65
62
  end
66
63
 
67
64
  def end_byte
68
- self.class::EndByte
65
+ SystemExclusive::DELIMITER[:finish]
69
66
  end
70
67
 
71
68
  def type_byte
72
- self.class::TypeByte
69
+ self.class::TYPE
73
70
  end
74
71
 
75
72
  # alternate method from
@@ -183,25 +180,41 @@ module MIDIMessage
183
180
 
184
181
  end
185
182
 
186
- # Convert raw MIDI data to SysEx message objects
187
- def self.new(*bytes)
183
+ module Builder
188
184
 
189
- start_status = bytes.shift
190
- end_status = bytes.pop
185
+ extend self
191
186
 
192
- if start_status == 0xF0 && end_status == 0xF7
187
+ # Convert raw MIDI data to a SysEx message object
188
+ def build(*bytes)
189
+ if is_sysex?(bytes)
193
190
 
194
- type_byte = bytes[3]
191
+ # if the 4th byte isn't status, we will just make this a Message object
192
+ # -- this may need some tweaking
193
+ message_class = get_message_class(bytes)
195
194
 
196
- # if the 4th byte isn't status, we will just make this a Message object -- this may need some tweaking
197
- if type_byte == 0x11
198
- msg_class = Request
199
- elsif type_byte == 0x12
200
- msg_class = Command
201
- else
202
- return Message.new(bytes)
195
+ if message_class.nil?
196
+ Message.new(bytes)
197
+ else
198
+ build_typed_message(message_class, bytes)
199
+ end
203
200
  end
204
201
 
202
+ end
203
+
204
+ private
205
+
206
+ def get_message_class(bytes)
207
+ [Request, Command].find { |klass| klass::TYPE == bytes[3] }
208
+ end
209
+
210
+ def is_sysex?(bytes)
211
+ bytes.shift == SystemExclusive::DELIMITER[:start] &&
212
+ bytes.pop == SystemExclusive::DELIMITER[:finish]
213
+ end
214
+
215
+ # Build a SysEx message object of the given type using the given bytes
216
+ def build_typed_message(message_class, bytes)
217
+ bytes = bytes.dup
205
218
  fixed_length_message_part = bytes.slice!(0,7)
206
219
 
207
220
  manufacturer_id = fixed_length_message_part[0]
@@ -213,8 +226,15 @@ module MIDIMessage
213
226
  value = bytes
214
227
 
215
228
  node = Node.new(manufacturer_id, :model_id => model_id, :device_id => device_id)
216
- msg_class.new(address, value, :checksum => checksum, :node => node)
229
+ message_class.new(address, value, :checksum => checksum, :node => node)
217
230
  end
231
+
232
+ end
233
+
234
+ # Convert raw MIDI data to a SysEx message object
235
+ # Shortcut to Builder.build
236
+ def self.new(*bytes)
237
+ Builder.build(*bytes)
218
238
  end
219
239
 
220
240
  end
@@ -3,6 +3,8 @@ module MIDIMessage
3
3
  # Common MIDI system message behavior
4
4
  module SystemMessage
5
5
 
6
+ STATUS = 0xF
7
+
6
8
  def self.included(base)
7
9
  base.send(:include, Message)
8
10
  end
@@ -13,7 +15,7 @@ module MIDIMessage
13
15
  # @param [Fixnum] byte The byte to strip of a redundant 0xF
14
16
  # @return [Fixnum] The remaining nibble
15
17
  def strip_redundant_nibble(byte)
16
- byte > 0xF ? (byte & 0x0F) : byte
18
+ byte > STATUS ? (byte & 0x0F) : byte
17
19
  end
18
20
 
19
21
  end
@@ -1,6 +1,6 @@
1
1
  require "helper"
2
2
 
3
- class ConstantTest < Minitest::Test
3
+ class MIDIMessage::ConstantTest < Minitest::Test
4
4
 
5
5
  context "Constant" do
6
6
 
@@ -33,6 +33,30 @@ class ConstantTest < Minitest::Test
33
33
 
34
34
  end
35
35
 
36
+ context "Name" do
37
+
38
+ context ".underscore" do
39
+
40
+ should "convert string" do
41
+ @result = MIDIMessage::Constant::Name.underscore("Control Change")
42
+ refute_nil @result
43
+ assert_equal "control_change", @result
44
+ end
45
+
46
+ end
47
+
48
+ context ".match?" do
49
+
50
+ should "match string" do
51
+ assert MIDIMessage::Constant::Name.match?("Control Change", :control_change)
52
+ assert MIDIMessage::Constant::Name.match?("Note", :note)
53
+ assert MIDIMessage::Constant::Name.match?("System Common", :system_common)
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
36
60
  context "Group" do
37
61
 
38
62
  context "#find" do
@@ -68,11 +92,65 @@ class ConstantTest < Minitest::Test
68
92
 
69
93
  end
70
94
 
95
+ context "MessageBuilder" do
96
+
97
+ context "#new" do
98
+
99
+ context "note on" do
100
+
101
+ setup do
102
+ @group = MIDIMessage::Constant::Group.find(:note)
103
+ @map = @group.find("C3")
104
+ @builder = MIDIMessage::Constant::MessageBuilder.new(MIDIMessage::NoteOn, @map)
105
+ end
106
+
107
+ should "build correct note" do
108
+ @note = @builder.new
109
+ refute_nil @note
110
+ assert_equal "C3", @note.name
111
+ end
112
+
113
+ end
114
+
115
+ context "cc" do
116
+
117
+ setup do
118
+ @group = MIDIMessage::Constant::Group.find(:control_change)
119
+ @map = @group.find("Modulation Wheel")
120
+ @builder = MIDIMessage::Constant::MessageBuilder.new(MIDIMessage::ControlChange, @map)
121
+ end
122
+
123
+ should "build correct cc" do
124
+ @cc = @builder.new
125
+ refute_nil @cc
126
+ assert_equal "Modulation Wheel", @cc.name
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+
135
+ context "Status" do
136
+
137
+ context ".find" do
138
+
139
+ should "find status" do
140
+ assert_equal 0x8, MIDIMessage::Constant::Status.find("Note Off")
141
+ assert_equal 0x9, MIDIMessage::Constant::Status.find("Note On")
142
+ assert_equal 0xB, MIDIMessage::Constant::Status["Control Change"]
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+
71
149
  context "Loader" do
72
150
 
73
151
  context "DSL" do
74
152
 
75
- context "#[]" do
153
+ context ".find" do
76
154
 
77
155
  context "note on" do
78
156
 
@@ -108,7 +186,7 @@ class ConstantTest < Minitest::Test
108
186
  context "cc" do
109
187
 
110
188
  setup do
111
- @message = MIDIMessage::ControlChange["Modulation Wheel"].new(2, 0x20)
189
+ @message = MIDIMessage::ControlChange.find("Modulation Wheel").new(2, 0x20)
112
190
  end
113
191
 
114
192
  should "create message object" do
@@ -1,129 +1,133 @@
1
1
  require "helper"
2
2
 
3
- class ContextTest < Minitest::Test
3
+ class MIDIMessage::ContextTest < Minitest::Test
4
4
 
5
5
  context "Context" do
6
6
 
7
- context "note off" do
7
+ context ".with" do
8
8
 
9
- setup do
10
- @message = MIDIMessage.with(:channel => 0, :velocity => 64) do
11
- note_off(55)
9
+ context "note off" do
10
+
11
+ setup do
12
+ @message = MIDIMessage.with(:channel => 0, :velocity => 64) do
13
+ note_off(55)
14
+ end
15
+ end
16
+
17
+ should "create message object" do
18
+ assert_equal(0, @message.channel)
19
+ assert_equal(55, @message.note)
20
+ assert_equal(64, @message.velocity)
21
+ assert_equal([128, 55, 64], @message.to_a)
22
+ assert_equal("803740", @message.to_bytestr)
12
23
  end
13
- end
14
24
 
15
- should "create message object" do
16
- assert_equal(0, @message.channel)
17
- assert_equal(55, @message.note)
18
- assert_equal(64, @message.velocity)
19
- assert_equal([128, 55, 64], @message.to_a)
20
- assert_equal("803740", @message.to_bytestr)
21
25
  end
22
26
 
23
- end
27
+ context "note on" do
24
28
 
25
- context "note on" do
29
+ setup do
30
+ @message = MIDIMessage.with(:channel => 0, :velocity => 64) do
31
+ note_on(55)
32
+ end
33
+ end
26
34
 
27
- setup do
28
- @message = MIDIMessage.with(:channel => 0, :velocity => 64) do
29
- note_on(55)
35
+ should "create message object" do
36
+ assert_equal(0, @message.channel)
37
+ assert_equal(55, @message.note)
38
+ assert_equal(64, @message.velocity)
39
+ assert_equal([144, 55, 64], @message.to_a)
40
+ assert_equal("903740", @message.to_bytestr)
30
41
  end
31
- end
32
42
 
33
- should "create message object" do
34
- assert_equal(0, @message.channel)
35
- assert_equal(55, @message.note)
36
- assert_equal(64, @message.velocity)
37
- assert_equal([144, 55, 64], @message.to_a)
38
- assert_equal("903740", @message.to_bytestr)
39
43
  end
40
44
 
41
- end
45
+ context "cc" do
42
46
 
43
- context "cc" do
47
+ setup do
48
+ @message = MIDIMessage::Context.with(:channel => 2) do
49
+ control_change(0x20, 0x30)
50
+ end
51
+ end
44
52
 
45
- setup do
46
- @message = MIDIMessage.with(:channel => 2) do
47
- control_change(0x20, 0x30)
53
+ should "create message object" do
54
+ assert_equal(@message.channel, 2)
55
+ assert_equal(0x20, @message.index)
56
+ assert_equal(0x30, @message.value)
57
+ assert_equal([0xB2, 0x20, 0x30], @message.to_a)
58
+ assert_equal("B22030", @message.to_bytestr)
48
59
  end
49
- end
50
60
 
51
- should "create message object" do
52
- assert_equal(@message.channel, 2)
53
- assert_equal(0x20, @message.index)
54
- assert_equal(0x30, @message.value)
55
- assert_equal([0xB2, 0x20, 0x30], @message.to_a)
56
- assert_equal("B22030", @message.to_bytestr)
57
61
  end
58
62
 
59
- end
63
+ context "polyphonic aftertouch" do
60
64
 
61
- context "polyphonic aftertouch" do
65
+ setup do
66
+ @message = MIDIMessage::Context.with(:channel => 1) do
67
+ polyphonic_aftertouch(0x40, 0x40)
68
+ end
69
+ end
62
70
 
63
- setup do
64
- @message = MIDIMessage.with(:channel => 1) do
65
- polyphonic_aftertouch(0x40, 0x40)
71
+ should "create message object" do
72
+ assert_equal(1, @message.channel)
73
+ assert_equal(0x40, @message.note)
74
+ assert_equal(0x40, @message.value)
75
+ assert_equal([0xA1, 0x40, 0x40], @message.to_a)
76
+ assert_equal("A14040", @message.to_bytestr)
66
77
  end
67
- end
68
78
 
69
- should "create message object" do
70
- assert_equal(1, @message.channel)
71
- assert_equal(0x40, @message.note)
72
- assert_equal(0x40, @message.value)
73
- assert_equal([0xA1, 0x40, 0x40], @message.to_a)
74
- assert_equal("A14040", @message.to_bytestr)
75
79
  end
76
80
 
77
- end
81
+ context "program change" do
78
82
 
79
- context "program change" do
83
+ setup do
84
+ @message = MIDIMessage.with(:channel => 3) do
85
+ program_change(0x40)
86
+ end
87
+ end
80
88
 
81
- setup do
82
- @message = MIDIMessage.with(:channel => 3) do
83
- program_change(0x40)
89
+ should "create message object" do
90
+ assert_equal(3, @message.channel)
91
+ assert_equal(0x40, @message.program)
92
+ assert_equal([0xC3, 0x40], @message.to_a)
93
+ assert_equal("C340", @message.to_bytestr)
84
94
  end
85
- end
86
95
 
87
- should "create message object" do
88
- assert_equal(3, @message.channel)
89
- assert_equal(0x40, @message.program)
90
- assert_equal([0xC3, 0x40], @message.to_a)
91
- assert_equal("C340", @message.to_bytestr)
92
96
  end
93
97
 
94
- end
98
+ context "channel aftertouch" do
95
99
 
96
- context "channel aftertouch" do
100
+ setup do
101
+ @message = MIDIMessage.with(:channel => 3) do
102
+ channel_aftertouch(0x50)
103
+ end
104
+ end
97
105
 
98
- setup do
99
- @message = MIDIMessage.with(:channel => 3) do
100
- channel_aftertouch(0x50)
106
+ should "create message object" do
107
+ assert_equal(3, @message.channel)
108
+ assert_equal(0x50, @message.value)
109
+ assert_equal([0xD3, 0x50], @message.to_a)
110
+ assert_equal("D350", @message.to_bytestr)
101
111
  end
102
- end
103
112
 
104
- should "create message object" do
105
- assert_equal(3, @message.channel)
106
- assert_equal(0x50, @message.value)
107
- assert_equal([0xD3, 0x50], @message.to_a)
108
- assert_equal("D350", @message.to_bytestr)
109
113
  end
110
114
 
111
- end
115
+ context "pitch bend" do
112
116
 
113
- context "pitch bend" do
117
+ setup do
118
+ @message = MIDIMessage.with(:channel => 0) do
119
+ pitch_bend(0x50, 0xA0)
120
+ end
121
+ end
114
122
 
115
- setup do
116
- @message = MIDIMessage.with(:channel => 0) do
117
- pitch_bend(0x50, 0xA0)
123
+ should "create message object" do
124
+ assert_equal(0, @message.channel)
125
+ assert_equal(0x50, @message.low)
126
+ assert_equal(0xA0, @message.high)
127
+ assert_equal([0xE0, 0x50, 0xA0], @message.to_a)
128
+ assert_equal("E050A0", @message.to_bytestr)
118
129
  end
119
- end
120
130
 
121
- should "create message object" do
122
- assert_equal(0, @message.channel)
123
- assert_equal(0x50, @message.low)
124
- assert_equal(0xA0, @message.high)
125
- assert_equal([0xE0, 0x50, 0xA0], @message.to_a)
126
- assert_equal("E050A0", @message.to_bytestr)
127
131
  end
128
132
 
129
133
  end