midi-message 0.4.4 → 0.4.5
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/LICENSE +1 -1
- data/README.md +16 -16
- data/lib/midi-message.rb +5 -5
- data/lib/midi-message/channel_message.rb +55 -33
- data/lib/midi-message/constant.rb +201 -45
- data/lib/midi-message/message.rb +36 -195
- data/lib/midi-message/messages.rb +211 -0
- data/lib/midi-message/system_message.rb +2 -2
- data/test/constants_test.rb +25 -30
- data/test/context_test.rb +2 -6
- data/test/helper.rb +3 -8
- data/test/{short_message_test.rb → message_test.rb} +44 -49
- data/test/mutability_test.rb +8 -13
- data/test/parser_test.rb +39 -44
- data/test/system_exclusive_test.rb +54 -59
- data/test/system_message_test.rb +21 -27
- metadata +85 -5
- data/lib/midi-message/short_message.rb +0 -137
data/lib/midi-message/message.rb
CHANGED
@@ -1,212 +1,53 @@
|
|
1
1
|
module MIDIMessage
|
2
2
|
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
ChannelPressure = ChannelAftertouch
|
15
|
-
|
16
|
-
#
|
17
|
-
# MIDI Control Change message
|
18
|
-
#
|
19
|
-
class ControlChange
|
20
|
-
|
21
|
-
include ChannelMessage
|
22
|
-
|
23
|
-
DATA = [:channel, :index, :value]
|
24
|
-
DISPLAY_NAME = "Control Change"
|
25
|
-
CONSTANT = { "Control Change" => :index }
|
26
|
-
|
27
|
-
end
|
28
|
-
Controller = ControlChange #shortcut
|
29
|
-
|
30
|
-
#
|
31
|
-
# MIDI Pitch Bend message
|
32
|
-
#
|
33
|
-
class PitchBend
|
34
|
-
|
35
|
-
include ChannelMessage
|
36
|
-
|
37
|
-
DATA = [:channel, :low, :high]
|
38
|
-
DISPLAY_NAME = "Pitch Bend"
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
#
|
43
|
-
# MIDI Polyphonic (note specific) Aftertouch message
|
44
|
-
#
|
45
|
-
class PolyphonicAftertouch
|
46
|
-
|
47
|
-
include ChannelMessage
|
48
|
-
|
49
|
-
DATA = [:channel, :note, :value]
|
50
|
-
DISPLAY_NAME = "Polyphonic Aftertouch"
|
51
|
-
CONSTANT = { "Note" => :note }
|
52
|
-
|
53
|
-
end
|
54
|
-
PolyAftertouch = PolyphonicAftertouch
|
55
|
-
PolyPressure = PolyphonicAftertouch
|
56
|
-
PolyphonicPressure = PolyphonicAftertouch
|
57
|
-
|
58
|
-
#
|
59
|
-
# MIDI Program Change message
|
60
|
-
#
|
61
|
-
class ProgramChange
|
62
|
-
|
63
|
-
include ChannelMessage
|
64
|
-
|
65
|
-
DATA = [:channel, :program]
|
66
|
-
DISPLAY_NAME = "Program Change"
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
#
|
71
|
-
# MIDI Note-Off message
|
72
|
-
#
|
73
|
-
class NoteOff
|
74
|
-
|
75
|
-
include NoteMessage
|
76
|
-
|
77
|
-
DATA = [:channel, :note, :velocity]
|
78
|
-
DISPLAY_NAME = "Note Off"
|
79
|
-
CONSTANT = { "Note" => :note }
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
#
|
84
|
-
# MIDI Note-On message
|
85
|
-
#
|
86
|
-
class NoteOn
|
87
|
-
|
88
|
-
include NoteMessage
|
89
|
-
|
90
|
-
DATA = [:channel, :note, :velocity]
|
91
|
-
DISPLAY_NAME = "Note On"
|
92
|
-
CONSTANT = { "Note" => :note }
|
93
|
-
|
94
|
-
# returns the NoteOff equivalent of this object
|
95
|
-
def to_note_off
|
96
|
-
NoteOff.new(channel, note, velocity)
|
3
|
+
# Common behavior amongst all Message types
|
4
|
+
module Message
|
5
|
+
|
6
|
+
# Initialize the message status
|
7
|
+
# @param [Fixnum] status_nibble_1 The first nibble of the status
|
8
|
+
# @param [Fixnum] status_nibble_2 The second nibble of the status
|
9
|
+
def initialize_message(status_nibble_1, status_nibble_2)
|
10
|
+
@status = [status_nibble_1, status_nibble_2]
|
11
|
+
populate_using_const
|
97
12
|
end
|
98
13
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
class SystemCommon
|
105
|
-
|
106
|
-
include SystemMessage
|
107
|
-
|
108
|
-
DISPLAY_NAME = "System Common"
|
109
|
-
|
110
|
-
attr_reader :data
|
111
|
-
|
112
|
-
def initialize(*args)
|
113
|
-
options = args.last.kind_of?(Hash) ? args.pop : {}
|
114
|
-
@const = options[:const]
|
115
|
-
id = @const.nil? ? args.shift : @const.value
|
116
|
-
id = strip_redundant_nibble(id)
|
117
|
-
initialize_short_message(0xF, id)
|
118
|
-
@data = args.slice(0..1)
|
14
|
+
# Byte array representation of the message eg [0x90, 0x40, 0x40] for NoteOn(0x40, 0x40)
|
15
|
+
# @return [Array<Fixnum>] The array of bytes in the MIDI message
|
16
|
+
def to_a
|
17
|
+
data = @data.nil? ? [] : [@data[0], @data[1]]
|
18
|
+
[(@status[0] << 4) + @status[1], *data].compact
|
119
19
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
include SystemMessage
|
129
|
-
|
130
|
-
DISPLAY_NAME = "System Realtime"
|
131
|
-
|
132
|
-
def initialize(*args)
|
133
|
-
options = args.last.kind_of?(Hash) ? args.pop : {}
|
134
|
-
@const = options[:const]
|
135
|
-
id = @const.nil? ? args.first : @const.value
|
136
|
-
id = strip_redundant_nibble(id)
|
137
|
-
initialize_short_message(0xF, id)
|
20
|
+
alias_method :to_byte_a, :to_a
|
21
|
+
alias_method :to_byte_array, :to_a
|
22
|
+
alias_method :to_bytes, :to_a
|
23
|
+
|
24
|
+
# String representation of the message's bytes eg "904040" for NoteOn(0x40, 0x40)
|
25
|
+
# @return [String] The bytes of the message as a string of hex bytes
|
26
|
+
def to_hex_s
|
27
|
+
TypeConversion.numeric_byte_array_to_hex_string(to_a)
|
138
28
|
end
|
29
|
+
alias_method :to_bytestr, :to_hex_s
|
139
30
|
|
140
|
-
def
|
141
|
-
|
31
|
+
def update
|
32
|
+
populate_using_const
|
142
33
|
end
|
143
34
|
|
144
|
-
|
145
|
-
|
146
|
-
module SystemExclusive
|
147
|
-
|
148
|
-
# A SysEx command message
|
149
|
-
# A command message is identified by having a status byte equal to 0x12
|
150
|
-
class Command
|
151
|
-
|
152
|
-
include SystemExclusive
|
153
|
-
|
154
|
-
attr_accessor :data
|
155
|
-
alias_method :value, :data
|
35
|
+
protected
|
156
36
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
@
|
162
|
-
data.first
|
163
|
-
else
|
164
|
-
data
|
165
|
-
end
|
166
|
-
initialize_sysex(address, options)
|
37
|
+
def populate_using_const
|
38
|
+
unless (info = Constant::Loader.get_info(self)).nil?
|
39
|
+
@const = info[:const]
|
40
|
+
@name = info[:name]
|
41
|
+
@verbose_name = info[:verbose_name]
|
167
42
|
end
|
168
|
-
|
169
43
|
end
|
170
|
-
|
171
|
-
# A SysEx request message
|
172
|
-
# A request message is identified by having a status byte equal to 0x11
|
173
|
-
class Request
|
174
44
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
TypeByte = 0x11
|
181
|
-
|
182
|
-
def initialize(address, size, options = {})
|
183
|
-
self.size = if size.kind_of?(Array) && size.count == 1
|
184
|
-
size.first
|
185
|
-
else
|
186
|
-
size
|
187
|
-
end
|
188
|
-
initialize_sysex(address, options)
|
189
|
-
end
|
190
|
-
|
191
|
-
def size=(value)
|
192
|
-
# accepts a Numeric or Array but
|
193
|
-
# must always store value as an array of three bytes
|
194
|
-
size = []
|
195
|
-
if value.kind_of?(Array) && value.size <= 3
|
196
|
-
size = value
|
197
|
-
elsif value.kind_of?(Numeric) && (value + 1) / 247 <= 2
|
198
|
-
size = []
|
199
|
-
div, mod = *value.divmod(247)
|
200
|
-
size << mod unless mod.zero?
|
201
|
-
div.times { size << 247 }
|
202
|
-
end
|
203
|
-
(3 - size.size).times { size.unshift 0 }
|
204
|
-
@size = size
|
205
|
-
end
|
206
|
-
|
45
|
+
def self.included(base)
|
46
|
+
base.send(:extend, Constant::Loader::DSL)
|
47
|
+
base.send(:include, MIDIMessage) # this enables ..kind_of?(MIDIMessage)
|
48
|
+
base.send(:attr_reader, :name, :status, :verbose_name)
|
207
49
|
end
|
208
50
|
|
209
51
|
end
|
210
|
-
|
211
|
-
end
|
212
52
|
|
53
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module MIDIMessage
|
2
|
+
|
3
|
+
#
|
4
|
+
# MIDI Channel Aftertouch message
|
5
|
+
#
|
6
|
+
class ChannelAftertouch
|
7
|
+
|
8
|
+
include ChannelMessage
|
9
|
+
|
10
|
+
DATA = [:channel, :value]
|
11
|
+
DISPLAY_NAME = "Channel Aftertouch"
|
12
|
+
|
13
|
+
end
|
14
|
+
ChannelPressure = ChannelAftertouch
|
15
|
+
|
16
|
+
#
|
17
|
+
# MIDI Control Change message
|
18
|
+
#
|
19
|
+
class ControlChange
|
20
|
+
|
21
|
+
include ChannelMessage
|
22
|
+
|
23
|
+
DATA = [:channel, :index, :value]
|
24
|
+
DISPLAY_NAME = "Control Change"
|
25
|
+
CONSTANT = { "Control Change" => :index }
|
26
|
+
|
27
|
+
end
|
28
|
+
Controller = ControlChange #shortcut
|
29
|
+
|
30
|
+
#
|
31
|
+
# MIDI Pitch Bend message
|
32
|
+
#
|
33
|
+
class PitchBend
|
34
|
+
|
35
|
+
include ChannelMessage
|
36
|
+
|
37
|
+
DATA = [:channel, :low, :high]
|
38
|
+
DISPLAY_NAME = "Pitch Bend"
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# MIDI Polyphonic (note specific) Aftertouch message
|
44
|
+
#
|
45
|
+
class PolyphonicAftertouch
|
46
|
+
|
47
|
+
include ChannelMessage
|
48
|
+
|
49
|
+
DATA = [:channel, :note, :value]
|
50
|
+
DISPLAY_NAME = "Polyphonic Aftertouch"
|
51
|
+
CONSTANT = { "Note" => :note }
|
52
|
+
|
53
|
+
end
|
54
|
+
PolyAftertouch = PolyphonicAftertouch
|
55
|
+
PolyPressure = PolyphonicAftertouch
|
56
|
+
PolyphonicPressure = PolyphonicAftertouch
|
57
|
+
|
58
|
+
#
|
59
|
+
# MIDI Program Change message
|
60
|
+
#
|
61
|
+
class ProgramChange
|
62
|
+
|
63
|
+
include ChannelMessage
|
64
|
+
|
65
|
+
DATA = [:channel, :program]
|
66
|
+
DISPLAY_NAME = "Program Change"
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# MIDI Note-Off message
|
72
|
+
#
|
73
|
+
class NoteOff
|
74
|
+
|
75
|
+
include NoteMessage
|
76
|
+
|
77
|
+
DATA = [:channel, :note, :velocity]
|
78
|
+
DISPLAY_NAME = "Note Off"
|
79
|
+
CONSTANT = { "Note" => :note }
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# MIDI Note-On message
|
85
|
+
#
|
86
|
+
class NoteOn
|
87
|
+
|
88
|
+
include NoteMessage
|
89
|
+
|
90
|
+
DATA = [:channel, :note, :velocity]
|
91
|
+
DISPLAY_NAME = "Note On"
|
92
|
+
CONSTANT = { "Note" => :note }
|
93
|
+
|
94
|
+
# returns the NoteOff equivalent of this object
|
95
|
+
def to_note_off
|
96
|
+
NoteOff.new(channel, note, velocity)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# MIDI System-Common message
|
103
|
+
#
|
104
|
+
class SystemCommon
|
105
|
+
|
106
|
+
include SystemMessage
|
107
|
+
|
108
|
+
DISPLAY_NAME = "System Common"
|
109
|
+
|
110
|
+
attr_reader :data
|
111
|
+
|
112
|
+
def initialize(*args)
|
113
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
114
|
+
@const = options[:const]
|
115
|
+
id = @const.nil? ? args.shift : @const.value
|
116
|
+
id = strip_redundant_nibble(id)
|
117
|
+
initialize_message(0xF, id)
|
118
|
+
@data = args.slice(0..1)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# MIDI System-Realtime message
|
125
|
+
#
|
126
|
+
class SystemRealtime
|
127
|
+
|
128
|
+
include SystemMessage
|
129
|
+
|
130
|
+
DISPLAY_NAME = "System Realtime"
|
131
|
+
|
132
|
+
def initialize(*args)
|
133
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
134
|
+
@const = options[:const]
|
135
|
+
id = @const.nil? ? args.first : @const.value
|
136
|
+
id = strip_redundant_nibble(id)
|
137
|
+
initialize_message(0xF, id)
|
138
|
+
end
|
139
|
+
|
140
|
+
def id
|
141
|
+
@status[1]
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
module SystemExclusive
|
147
|
+
|
148
|
+
# A SysEx command message
|
149
|
+
# A command message is identified by having a status byte equal to 0x12
|
150
|
+
class Command
|
151
|
+
|
152
|
+
include SystemExclusive
|
153
|
+
|
154
|
+
attr_accessor :data
|
155
|
+
alias_method :value, :data
|
156
|
+
|
157
|
+
TypeByte = 0x12
|
158
|
+
|
159
|
+
def initialize(address, data, options = {})
|
160
|
+
# store as a byte if it's a single byte
|
161
|
+
@data = if data.kind_of?(Array) && data.length == 1
|
162
|
+
data.first
|
163
|
+
else
|
164
|
+
data
|
165
|
+
end
|
166
|
+
initialize_sysex(address, options)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
# A SysEx request message
|
172
|
+
# A request message is identified by having a status byte equal to 0x11
|
173
|
+
class Request
|
174
|
+
|
175
|
+
include SystemExclusive
|
176
|
+
|
177
|
+
attr_reader :size
|
178
|
+
alias_method :value, :size
|
179
|
+
|
180
|
+
TypeByte = 0x11
|
181
|
+
|
182
|
+
def initialize(address, size, options = {})
|
183
|
+
self.size = if size.kind_of?(Array) && size.count == 1
|
184
|
+
size.first
|
185
|
+
else
|
186
|
+
size
|
187
|
+
end
|
188
|
+
initialize_sysex(address, options)
|
189
|
+
end
|
190
|
+
|
191
|
+
def size=(value)
|
192
|
+
# accepts a Numeric or Array but
|
193
|
+
# must always store value as an array of three bytes
|
194
|
+
size = []
|
195
|
+
if value.kind_of?(Array) && value.size <= 3
|
196
|
+
size = value
|
197
|
+
elsif value.kind_of?(Numeric) && (value + 1) / 247 <= 2
|
198
|
+
size = []
|
199
|
+
div, mod = *value.divmod(247)
|
200
|
+
size << mod unless mod.zero?
|
201
|
+
div.times { size << 247 }
|
202
|
+
end
|
203
|
+
(3 - size.size).times { size.unshift 0 }
|
204
|
+
@size = size
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|