midi-message 0.4.8 → 0.4.9

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.
@@ -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