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.
- checksums.yaml +4 -4
- data/lib/midi-message.rb +1 -1
- data/lib/midi-message/channel_message.rb +76 -53
- data/lib/midi-message/constant.rb +34 -7
- data/lib/midi-message/context.rb +11 -1
- data/lib/midi-message/message.rb +11 -3
- data/lib/midi-message/messages.rb +45 -15
- data/lib/midi-message/parser.rb +65 -22
- data/lib/midi-message/system_exclusive.rb +42 -22
- data/lib/midi-message/system_message.rb +3 -1
- data/test/constant_test.rb +81 -3
- data/test/context_test.rb +86 -82
- data/test/message_test.rb +28 -122
- data/test/messages_test.rb +139 -0
- data/test/parser_test.rb +1 -1
- data/test/system_exclusive_test.rb +245 -98
- data/test/system_message_test.rb +95 -55
- metadata +3 -3
- data/test/mutability_test.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96815929056b3d199312f9d0ea055edcdade2313
|
4
|
+
data.tar.gz: bd58cc3e59de12731d9541dcf4cfb00ef81b4ef7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61721e744f4c482f7a7318866ef2e61fb23d7d7b7bf21311070b67eb7d37a72a7d4f788b5b1dd87d24877b098bed676506871019fb795382803bfb8e5f872da1
|
7
|
+
data.tar.gz: 467155c933d12d8d7de50b65e050013685eb28ab4b663187fa3eb23a88b35b2eb86c0090ef843505b7d57832b3f92bad4143f2bbd6a9516ca5d6f6d02e02ff69
|
data/lib/midi-message.rb
CHANGED
@@ -10,13 +10,13 @@ module MIDIMessage
|
|
10
10
|
# Shortcut to RawChannelMessage.new
|
11
11
|
# aka build a ChannelMessage from raw nibbles and bytes
|
12
12
|
# eg ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
|
13
|
-
# @param [*
|
13
|
+
# @param [*Fixnum] data The status nibbles and data bytes
|
14
14
|
# @return [RawChannelMessage] The resulting RawChannelMessage object
|
15
15
|
def self.new(*data, &block)
|
16
16
|
Message.new(*data, &block)
|
17
17
|
end
|
18
18
|
|
19
|
-
# @param [*
|
19
|
+
# @param [*Fixnum] data The status nibbles and data bytes
|
20
20
|
def initialize(*data)
|
21
21
|
data = data.dup
|
22
22
|
options = data.last.kind_of?(Hash) ? data.pop : {}
|
@@ -24,13 +24,6 @@ module MIDIMessage
|
|
24
24
|
initialize_channel_message(self.class.type_for_status, *data)
|
25
25
|
end
|
26
26
|
|
27
|
-
# Decorates the object with the particular properties for its type
|
28
|
-
# @return [Boolean]
|
29
|
-
def initialize_properties
|
30
|
-
properties = self.class.properties
|
31
|
-
add_properties(properties) unless properties.nil?
|
32
|
-
end
|
33
|
-
|
34
27
|
private
|
35
28
|
|
36
29
|
def self.included(base)
|
@@ -38,62 +31,92 @@ module MIDIMessage
|
|
38
31
|
base.send(:extend, ClassMethods)
|
39
32
|
end
|
40
33
|
|
34
|
+
# Add the given constant to message data
|
41
35
|
def add_constant_value(constant, data)
|
42
36
|
index = Constant::Loader.get_index(self)
|
43
37
|
data.insert(index, constant.value)
|
44
38
|
end
|
45
39
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
40
|
+
# Assign the message data
|
41
|
+
def assign_data(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
42
|
+
@status = [status_nibble_1, status_nibble_2]
|
43
|
+
@data = [data_byte_1]
|
44
|
+
@data[1] = data_byte_2 if self.class.second_data_byte?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Initialize the message: assign data, decorate with accessors
|
48
|
+
def initialize_channel_message(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
49
|
+
assign_data(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2)
|
50
|
+
Accessors.initialize(self) unless self.class.properties.nil?
|
51
|
+
initialize_message(status_nibble_1, status_nibble_2)
|
52
|
+
end
|
53
|
+
|
54
|
+
class Accessors
|
55
|
+
|
56
|
+
SCHEMA = [
|
51
57
|
{ :name => :status, :index => 1 }, # second status nibble
|
52
58
|
{ :name => :data, :index => 0 }, # first data byte
|
53
59
|
{ :name => :data, :index => 1 } # second data byte
|
54
|
-
]
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
].freeze
|
61
|
+
|
62
|
+
# @param [Class] klass
|
63
|
+
# @return [Class]
|
64
|
+
def self.decorate(klass)
|
65
|
+
decorator = new(klass)
|
66
|
+
decorator.decorate
|
60
67
|
end
|
61
|
-
has_properties
|
62
|
-
end
|
63
68
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
# Initialize a message object with it's properties
|
70
|
+
# @param [MIDIMessage] message
|
71
|
+
# @return [Boolean]
|
72
|
+
def self.initialize(message)
|
73
|
+
message.class.properties.each_with_index do |property, i|
|
74
|
+
data_mapping = SCHEMA[i]
|
75
|
+
container = message.send(data_mapping[:name])
|
76
|
+
index = data_mapping[:index]
|
77
|
+
message.send(:instance_variable_set, "@#{property.to_s}", container[index])
|
78
|
+
end
|
79
|
+
true
|
80
|
+
end
|
75
81
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
# @param [Class] klass
|
83
|
+
def initialize(klass)
|
84
|
+
@klass = klass
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Class]
|
88
|
+
def decorate
|
89
|
+
@klass.properties.each_with_index do |property, i|
|
90
|
+
data_mapping = SCHEMA[i]
|
91
|
+
define_getter(property)
|
92
|
+
define_setter(property, data_mapping)
|
93
|
+
end
|
94
|
+
@klass
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# @param [Symbol, String] property
|
100
|
+
# @return [Boolean]
|
101
|
+
def define_getter(property)
|
102
|
+
@klass.send(:attr_reader, property)
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param [Symbol, String] property
|
107
|
+
# @param [Hash] mapping
|
108
|
+
# @return [Boolean]
|
109
|
+
def define_setter(property, mapping)
|
110
|
+
index = mapping[:index]
|
111
|
+
@klass.send(:define_method, "#{property.to_s}=") do |value|
|
112
|
+
send(:instance_variable_set, "@#{property.to_s}", value)
|
113
|
+
send(mapping[:name])[index] = value
|
114
|
+
send(:update)
|
115
|
+
return self
|
116
|
+
end
|
117
|
+
true
|
87
118
|
end
|
88
|
-
true
|
89
|
-
end
|
90
119
|
|
91
|
-
def initialize_channel_message(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
92
|
-
@status = [status_nibble_1, status_nibble_2]
|
93
|
-
@data = [data_byte_1]
|
94
|
-
@data[1] = data_byte_2 if self.class.second_data_byte?
|
95
|
-
initialize_properties
|
96
|
-
initialize_message(status_nibble_1, status_nibble_2)
|
97
120
|
end
|
98
121
|
|
99
122
|
# For defining Channel Message class types
|
@@ -124,7 +147,7 @@ module MIDIMessage
|
|
124
147
|
|
125
148
|
# Build a Channel Mssage from raw nibbles and bytes
|
126
149
|
# eg ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
|
127
|
-
# @param [*
|
150
|
+
# @param [*Fixnum] data The status nibbles and data bytes
|
128
151
|
# @return [RawChannelMessage] The resulting RawChannelMessage object
|
129
152
|
def initialize(*data)
|
130
153
|
initialize_channel_message(*data)
|
@@ -22,6 +22,27 @@ module MIDIMessage
|
|
22
22
|
map.value
|
23
23
|
end
|
24
24
|
|
25
|
+
module Name
|
26
|
+
|
27
|
+
extend self
|
28
|
+
|
29
|
+
# eg "Control Change" -> "control_change"
|
30
|
+
# @param [Symbol, String] string
|
31
|
+
# @return [String]
|
32
|
+
def underscore(string)
|
33
|
+
string.to_s.downcase.gsub(/(\ )+/, "_")
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [Symbol, String] key
|
37
|
+
# @param [Symbol, String] other
|
38
|
+
# @return [Boolean]
|
39
|
+
def match?(key, other)
|
40
|
+
match_key = key.to_s.downcase
|
41
|
+
[match_key, Name.underscore(match_key)].include?(other.to_s.downcase)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
25
46
|
# MIDI Constant container
|
26
47
|
class Group
|
27
48
|
|
@@ -38,14 +59,14 @@ module MIDIMessage
|
|
38
59
|
# @param [String, Symbol] name
|
39
60
|
# @return [Constant::Map]
|
40
61
|
def find(name)
|
41
|
-
@constants.find { |const| const.key
|
62
|
+
@constants.find { |const| Name.match?(const.key, name) }
|
42
63
|
end
|
43
64
|
|
44
65
|
# Find a constant by its value
|
45
66
|
# @param [Object] value
|
46
67
|
# @return [Constant::Map]
|
47
68
|
def find_by_value(value)
|
48
|
-
@constants.find { |const| const.value
|
69
|
+
@constants.find { |const| Name.match?(const.value, value) }
|
49
70
|
end
|
50
71
|
|
51
72
|
class << self
|
@@ -62,7 +83,7 @@ module MIDIMessage
|
|
62
83
|
# @return [ConstantGroup]
|
63
84
|
def find(key)
|
64
85
|
ensure_initialized
|
65
|
-
@groups.find { |
|
86
|
+
@groups.find { |group| Name.match?(group.key, key) }
|
66
87
|
end
|
67
88
|
alias_method :[], :find
|
68
89
|
|
@@ -78,7 +99,9 @@ module MIDIMessage
|
|
78
99
|
# @return [Boolean]
|
79
100
|
def populate_dictionary
|
80
101
|
if @dict.nil?
|
81
|
-
|
102
|
+
file = File.expand_path('../../midi.yml', __FILE__)
|
103
|
+
@dict = YAML.load_file(file)
|
104
|
+
@dict.freeze
|
82
105
|
true
|
83
106
|
end
|
84
107
|
end
|
@@ -113,7 +136,7 @@ module MIDIMessage
|
|
113
136
|
class MessageBuilder
|
114
137
|
|
115
138
|
# @param [MIDIMessage] klass The message class to build
|
116
|
-
# @param [
|
139
|
+
# @param [MIDIMessage::Constant::Map] const The constant to build the message with
|
117
140
|
def initialize(klass, const)
|
118
141
|
@klass = klass
|
119
142
|
@const = const
|
@@ -132,13 +155,16 @@ module MIDIMessage
|
|
132
155
|
# Shortcuts for dealing with message status
|
133
156
|
module Status
|
134
157
|
|
158
|
+
extend self
|
159
|
+
|
135
160
|
# The value of the Status constant with the name status_name
|
136
161
|
# @param [String] status_name The key to use to look up a constant value
|
137
162
|
# @return [String] The constant value that was looked up
|
138
|
-
def
|
163
|
+
def find(status_name)
|
139
164
|
const = Constant.find("Status", status_name)
|
140
165
|
const.value unless const.nil?
|
141
166
|
end
|
167
|
+
alias_method :[], :find
|
142
168
|
|
143
169
|
end
|
144
170
|
|
@@ -219,10 +245,11 @@ module MIDIMessage
|
|
219
245
|
# This returns a MessageBuilder for the class, preloaded with the selected const
|
220
246
|
# @param [String, Symbol] const_name The constant key to use to build the message
|
221
247
|
# @return [MIDIMessage::MessageBuilder] A MessageBuilder object for the passed in constant
|
222
|
-
def
|
248
|
+
def find(const_name)
|
223
249
|
const = get_constant(const_name.to_s)
|
224
250
|
MessageBuilder.new(self, const) unless const.nil?
|
225
251
|
end
|
252
|
+
alias_method :[], :find
|
226
253
|
|
227
254
|
end
|
228
255
|
|
data/lib/midi-message/context.rb
CHANGED
@@ -5,6 +5,15 @@ module MIDIMessage
|
|
5
5
|
|
6
6
|
attr_accessor :channel, :velocity
|
7
7
|
|
8
|
+
# Open a context with the given options
|
9
|
+
# @param [Hash] options
|
10
|
+
# @param [Proc] block
|
11
|
+
# @option options [Fixnum] :channel
|
12
|
+
# @option options [Fixnum] :velocity
|
13
|
+
def self.with(options = {}, &block)
|
14
|
+
new(options, &block).instance_eval(&block)
|
15
|
+
end
|
16
|
+
|
8
17
|
# @param [Hash] options
|
9
18
|
# @option options [Fixnum] :channel
|
10
19
|
# @option options [Fixnum] :velocity
|
@@ -129,12 +138,13 @@ module MIDIMessage
|
|
129
138
|
|
130
139
|
end
|
131
140
|
|
141
|
+
# Shortcut to MIDIMessage::Context.with
|
132
142
|
# @param [Hash] options
|
133
143
|
# @param [Proc] block
|
134
144
|
# @option options [Fixnum] :channel
|
135
145
|
# @option options [Fixnum] :velocity
|
136
146
|
def self.with_context(options = {}, &block)
|
137
|
-
Context.
|
147
|
+
Context.with(options, &block)
|
138
148
|
end
|
139
149
|
class << self
|
140
150
|
alias_method :with, :with_context
|
data/lib/midi-message/message.rb
CHANGED
@@ -14,8 +14,9 @@ module MIDIMessage
|
|
14
14
|
# Byte array representation of the message eg [0x90, 0x40, 0x40] for NoteOn(0x40, 0x40)
|
15
15
|
# @return [Array<Fixnum>] The array of bytes in the MIDI message
|
16
16
|
def to_a
|
17
|
-
data =
|
18
|
-
|
17
|
+
data = [@data[0], @data[1]] unless @data.nil?
|
18
|
+
data ||= []
|
19
|
+
[status_as_byte, *data].compact
|
19
20
|
end
|
20
21
|
alias_method :to_byte_a, :to_a
|
21
22
|
alias_method :to_byte_array, :to_a
|
@@ -32,7 +33,14 @@ module MIDIMessage
|
|
32
33
|
populate_using_const
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
+
private
|
37
|
+
|
38
|
+
# Convert the status nibbles to a single byte
|
39
|
+
# Eg [0x9, 0xF] -> 0x9F
|
40
|
+
# @return [Fixnum]
|
41
|
+
def status_as_byte
|
42
|
+
(@status[0] << 4) + @status[1]
|
43
|
+
end
|
36
44
|
|
37
45
|
def populate_using_const
|
38
46
|
unless (info = Constant::Loader.get_info(self)).nil?
|
@@ -7,9 +7,12 @@ module MIDIMessage
|
|
7
7
|
|
8
8
|
include ChannelMessage
|
9
9
|
|
10
|
-
|
10
|
+
STATUS = 0xD
|
11
|
+
DATA = [:channel, :value].freeze
|
11
12
|
DISPLAY_NAME = "Channel Aftertouch"
|
12
13
|
|
14
|
+
ChannelMessage::Accessors.decorate(self)
|
15
|
+
|
13
16
|
end
|
14
17
|
ChannelPressure = ChannelAftertouch
|
15
18
|
|
@@ -20,9 +23,12 @@ module MIDIMessage
|
|
20
23
|
|
21
24
|
include ChannelMessage
|
22
25
|
|
23
|
-
|
26
|
+
STATUS = 0xB
|
27
|
+
DATA = [:channel, :index, :value].freeze
|
24
28
|
DISPLAY_NAME = "Control Change"
|
25
|
-
CONSTANT = { "Control Change" => :index }
|
29
|
+
CONSTANT = { "Control Change" => :index }.freeze
|
30
|
+
|
31
|
+
ChannelMessage::Accessors.decorate(self)
|
26
32
|
|
27
33
|
end
|
28
34
|
Controller = ControlChange #shortcut
|
@@ -34,9 +40,12 @@ module MIDIMessage
|
|
34
40
|
|
35
41
|
include ChannelMessage
|
36
42
|
|
37
|
-
|
43
|
+
STATUS = 0xE
|
44
|
+
DATA = [:channel, :low, :high].freeze
|
38
45
|
DISPLAY_NAME = "Pitch Bend"
|
39
46
|
|
47
|
+
ChannelMessage::Accessors.decorate(self)
|
48
|
+
|
40
49
|
end
|
41
50
|
|
42
51
|
#
|
@@ -46,9 +55,12 @@ module MIDIMessage
|
|
46
55
|
|
47
56
|
include ChannelMessage
|
48
57
|
|
49
|
-
|
58
|
+
STATUS = 0xA
|
59
|
+
DATA = [:channel, :note, :value].freeze
|
50
60
|
DISPLAY_NAME = "Polyphonic Aftertouch"
|
51
|
-
CONSTANT = { "Note" => :note }
|
61
|
+
CONSTANT = { "Note" => :note }.freeze
|
62
|
+
|
63
|
+
ChannelMessage::Accessors.decorate(self)
|
52
64
|
|
53
65
|
end
|
54
66
|
PolyAftertouch = PolyphonicAftertouch
|
@@ -62,9 +74,12 @@ module MIDIMessage
|
|
62
74
|
|
63
75
|
include ChannelMessage
|
64
76
|
|
65
|
-
|
77
|
+
STATUS = 0xC
|
78
|
+
DATA = [:channel, :program].freeze
|
66
79
|
DISPLAY_NAME = "Program Change"
|
67
80
|
|
81
|
+
ChannelMessage::Accessors.decorate(self)
|
82
|
+
|
68
83
|
end
|
69
84
|
|
70
85
|
#
|
@@ -74,9 +89,12 @@ module MIDIMessage
|
|
74
89
|
|
75
90
|
include NoteMessage
|
76
91
|
|
77
|
-
|
92
|
+
STATUS = 0x8
|
93
|
+
DATA = [:channel, :note, :velocity].freeze
|
78
94
|
DISPLAY_NAME = "Note Off"
|
79
|
-
CONSTANT = { "Note" => :note }
|
95
|
+
CONSTANT = { "Note" => :note }.freeze
|
96
|
+
|
97
|
+
ChannelMessage::Accessors.decorate(self)
|
80
98
|
|
81
99
|
end
|
82
100
|
|
@@ -87,9 +105,12 @@ module MIDIMessage
|
|
87
105
|
|
88
106
|
include NoteMessage
|
89
107
|
|
90
|
-
|
108
|
+
STATUS = 0x9
|
109
|
+
DATA = [:channel, :note, :velocity].freeze
|
91
110
|
DISPLAY_NAME = "Note On"
|
92
|
-
CONSTANT = { "Note" => :note }
|
111
|
+
CONSTANT = { "Note" => :note }.freeze
|
112
|
+
|
113
|
+
ChannelMessage::Accessors.decorate(self)
|
93
114
|
|
94
115
|
# returns the NoteOff equivalent of this object
|
95
116
|
def to_note_off
|
@@ -105,6 +126,7 @@ module MIDIMessage
|
|
105
126
|
|
106
127
|
include SystemMessage
|
107
128
|
|
129
|
+
ID = 0x1..0x6
|
108
130
|
DISPLAY_NAME = "System Common"
|
109
131
|
|
110
132
|
attr_reader :data
|
@@ -114,7 +136,7 @@ module MIDIMessage
|
|
114
136
|
@const = options[:const]
|
115
137
|
id = @const.nil? ? args.shift : @const.value
|
116
138
|
id = strip_redundant_nibble(id)
|
117
|
-
initialize_message(
|
139
|
+
initialize_message(SystemMessage::STATUS, id)
|
118
140
|
@data = args.slice(0..1)
|
119
141
|
end
|
120
142
|
|
@@ -127,6 +149,7 @@ module MIDIMessage
|
|
127
149
|
|
128
150
|
include SystemMessage
|
129
151
|
|
152
|
+
ID = 0x8..0xF
|
130
153
|
DISPLAY_NAME = "System Realtime"
|
131
154
|
|
132
155
|
def initialize(*args)
|
@@ -134,7 +157,7 @@ module MIDIMessage
|
|
134
157
|
@const = options[:const]
|
135
158
|
id = @const.nil? ? args.first : @const.value
|
136
159
|
id = strip_redundant_nibble(id)
|
137
|
-
initialize_message(
|
160
|
+
initialize_message(SystemMessage::STATUS, id)
|
138
161
|
end
|
139
162
|
|
140
163
|
def id
|
@@ -145,6 +168,13 @@ module MIDIMessage
|
|
145
168
|
|
146
169
|
module SystemExclusive
|
147
170
|
|
171
|
+
ID = 0x0
|
172
|
+
DELIMITER = {
|
173
|
+
:start => 0xF0,
|
174
|
+
:finish => 0xF7
|
175
|
+
}
|
176
|
+
DISPLAY_NAME = "System Exclusive"
|
177
|
+
|
148
178
|
# A SysEx command message
|
149
179
|
# A command message is identified by having a status byte equal to 0x12
|
150
180
|
class Command
|
@@ -154,7 +184,7 @@ module MIDIMessage
|
|
154
184
|
attr_accessor :data
|
155
185
|
alias_method :value, :data
|
156
186
|
|
157
|
-
|
187
|
+
TYPE = 0x12
|
158
188
|
|
159
189
|
def initialize(address, data, options = {})
|
160
190
|
# store as a byte if it's a single byte
|
@@ -177,7 +207,7 @@ module MIDIMessage
|
|
177
207
|
attr_reader :size
|
178
208
|
alias_method :value, :size
|
179
209
|
|
180
|
-
|
210
|
+
TYPE = 0x11
|
181
211
|
|
182
212
|
def initialize(address, size, options = {})
|
183
213
|
self.size = if size.kind_of?(Array) && size.count == 1
|