midi-message 0.4.4 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|