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