midilib 2.0.2 → 3.0.1
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 +7 -0
- data/ChangeLog +2 -1
- data/Credits +44 -2
- data/README.rdoc +42 -33
- data/Rakefile +36 -53
- data/TODO.rdoc +13 -2
- data/examples/from_scratch.rb +4 -6
- data/examples/measures_mbt.rb +11 -11
- data/examples/print_program_changes.rb +11 -11
- data/examples/reader2text.rb +191 -190
- data/examples/seq2text.rb +18 -18
- data/examples/split.rb +21 -20
- data/examples/strings.rb +15 -15
- data/examples/transpose.rb +41 -42
- data/install.rb +53 -34
- data/lib/midilib/consts.rb +406 -408
- data/lib/midilib/event.rb +335 -306
- data/lib/midilib/info.rb +5 -7
- data/lib/midilib/io/midifile.rb +424 -452
- data/lib/midilib/io/seqreader.rb +187 -192
- data/lib/midilib/io/seqwriter.rb +151 -147
- data/lib/midilib/measure.rb +78 -80
- data/lib/midilib/mergesort.rb +39 -0
- data/lib/midilib/sequence.rb +99 -86
- data/lib/midilib/track.rb +71 -118
- data/lib/midilib/utils.rb +17 -20
- data/lib/midilib.rb +5 -5
- data/test/event_equality.rb +50 -52
- data/test/test_event.rb +120 -124
- data/test/test_io.rb +107 -40
- data/test/test_mergesort.rb +37 -0
- data/test/test_midifile.rb +6 -19
- data/test/test_sequence.rb +64 -52
- data/test/test_track.rb +126 -155
- data/test/test_varlen.rb +23 -27
- metadata +20 -22
data/lib/midilib/io/midifile.rb
CHANGED
@@ -1,467 +1,439 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
if RUBY_VERSION < '1.9'
|
4
|
-
class IO
|
5
|
-
def readbyte
|
6
|
-
c = getc()
|
7
|
-
raise 'unexpected EOF' unless c
|
8
|
-
c
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
1
|
+
require_relative '../consts'
|
12
2
|
|
13
3
|
module MIDI
|
4
|
+
module IO
|
5
|
+
# A MIDIFile parses a MIDI file and calls methods when it sees MIDI events.
|
6
|
+
# Most of the methods are stubs. To do anything interesting with the events,
|
7
|
+
# override these methods (those between the "The rest of these are NOPs by
|
8
|
+
# default" and "End of NOPs" comments).
|
9
|
+
#
|
10
|
+
# See SeqReader for a subclass that uses these methods to create Event
|
11
|
+
# objects.
|
12
|
+
class MIDIFile
|
13
|
+
MThd_BYTE_ARRAY = [77, 84, 104, 100] # "MThd"
|
14
|
+
MTrk_BYTE_ARRAY = [77, 84, 114, 107] # "MTrk"
|
15
|
+
|
16
|
+
# This array is indexed by the high half of a status byte. Its
|
17
|
+
# value is either the number of bytes needed (1 or 2) for a channel
|
18
|
+
# message, or 0 if it's not a channel message.
|
19
|
+
NUM_DATA_BYTES = [
|
20
|
+
0, 0, 0, 0, 0, 0, 0, 0, # 0x00 - 0x70
|
21
|
+
2, 2, 2, 2, 1, 1, 2, 0 # 0x80 - 0xf0
|
22
|
+
]
|
23
|
+
|
24
|
+
attr_accessor :curr_ticks, :ticks_so_far, :bytes_to_be_read, :no_merge, :skip_init, :raw_var_num_data, :raw_data # Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
|
14
25
|
|
15
|
-
|
16
|
-
|
17
|
-
# A MIDIFile parses a MIDI file and calls methods when it sees MIDI events.
|
18
|
-
# Most of the methods are stubs. To do anything interesting with the events,
|
19
|
-
# override these methods (those between the "The rest of these are NOPs by
|
20
|
-
# default" and "End of NOPs" comments).
|
21
|
-
#
|
22
|
-
# See SeqReader for a subclass that uses these methods to create Event
|
23
|
-
# objects.
|
24
|
-
class MIDIFile
|
25
|
-
|
26
|
-
MThd_BYTE_ARRAY = [77, 84, 104, 100] # "MThd"
|
27
|
-
MTrk_BYTE_ARRAY = [77, 84, 114, 107] # "MTrk"
|
28
|
-
|
29
|
-
# This array is indexed by the high half of a status byte. Its
|
30
|
-
# value is either the number of bytes needed (1 or 2) for a channel
|
31
|
-
# message, or 0 if it's not a channel message.
|
32
|
-
NUM_DATA_BYTES = [
|
33
|
-
0, 0, 0, 0, 0, 0, 0, 0, # 0x00 - 0x70
|
34
|
-
2, 2, 2, 2, 1, 1, 2, 0 # 0x80 - 0xf0
|
35
|
-
]
|
36
|
-
|
37
|
-
attr_accessor :curr_ticks # Current time, from delta-time in MIDI file
|
38
|
-
attr_accessor :ticks_so_far # Number of delta-time ticks so far
|
39
|
-
attr_accessor :bytes_to_be_read # Counts number of bytes expected
|
40
|
-
|
41
|
-
attr_accessor :no_merge # true means continued sysex are not collapsed
|
42
|
-
attr_accessor :skip_init # true if initial garbage should be skipped
|
43
|
-
|
44
|
-
# Raw data info
|
45
|
-
attr_accessor :raw_time_stamp_data
|
46
|
-
attr_accessor :raw_var_num_data
|
47
|
-
attr_accessor :raw_data
|
48
|
-
|
49
|
-
def initialize
|
50
|
-
@no_merge = false
|
51
|
-
@skip_init = true
|
52
|
-
@io = nil
|
53
|
-
@bytes_to_be_read = 0
|
54
|
-
@msg_buf = nil
|
55
|
-
end
|
56
|
-
|
57
|
-
# The only public method. Each MIDI event in the file causes a
|
58
|
-
# method to be called.
|
59
|
-
def read_from(io)
|
60
|
-
error('must specify non-nil input stream') if io.nil?
|
61
|
-
@io = io
|
62
|
-
|
63
|
-
ntrks = read_header()
|
64
|
-
error('No tracks!') if ntrks <= 0
|
65
|
-
|
66
|
-
ntrks.times { read_track() }
|
67
|
-
end
|
68
|
-
|
69
|
-
# This default getc implementation tries to read a single byte
|
70
|
-
# from io and returns it as an integer.
|
71
|
-
def getc
|
72
|
-
@bytes_to_be_read -= 1
|
73
|
-
@io.readbyte()
|
74
|
-
end
|
75
|
-
|
76
|
-
# Return the next +n+ bytes from @io as an array.
|
77
|
-
def get_bytes(n)
|
78
|
-
buf = []
|
79
|
-
n.times { buf << getc() }
|
80
|
-
buf
|
81
|
-
end
|
82
|
-
|
83
|
-
# The default error handler.
|
84
|
-
def error(str)
|
85
|
-
loc = @io.tell() - 1
|
86
|
-
raise "#{self.class.name} error at byte #{loc} (0x#{'%02x' % loc}): #{str}"
|
87
|
-
end
|
88
|
-
|
89
|
-
# The rest of these are NOPs by default.
|
90
|
-
|
91
|
-
# MIDI header.
|
92
|
-
def header(format, ntrks, division)
|
93
|
-
end
|
94
|
-
|
95
|
-
def start_track(bytes_to_be_read)
|
96
|
-
end
|
97
|
-
|
98
|
-
def end_track()
|
99
|
-
end
|
100
|
-
|
101
|
-
def note_on(chan, note, vel)
|
102
|
-
end
|
103
|
-
|
104
|
-
def note_off(chan, note, vel)
|
105
|
-
end
|
106
|
-
|
107
|
-
def pressure(chan, note, press)
|
108
|
-
end
|
109
|
-
|
110
|
-
def controller(chan, control, value)
|
111
|
-
end
|
112
|
-
|
113
|
-
def pitch_bend(chan, msb, lsb)
|
114
|
-
end
|
115
|
-
|
116
|
-
def program(chan, program)
|
117
|
-
end
|
118
|
-
|
119
|
-
def chan_pressure(chan, press)
|
120
|
-
end
|
121
|
-
|
122
|
-
def sysex(msg)
|
123
|
-
end
|
124
|
-
|
125
|
-
def meta_misc(type, msg)
|
126
|
-
end
|
127
|
-
|
128
|
-
def sequencer_specific(type, msg)
|
129
|
-
end
|
130
|
-
|
131
|
-
def sequence_number(num)
|
132
|
-
end
|
133
|
-
|
134
|
-
def text(type, msg)
|
135
|
-
end
|
136
|
-
|
137
|
-
def eot()
|
138
|
-
end
|
139
|
-
|
140
|
-
def time_signature(numer, denom, clocks, qnotes)
|
141
|
-
end
|
142
|
-
|
143
|
-
def smpte(hour, min, sec, frame, fract)
|
144
|
-
end
|
145
|
-
|
146
|
-
def tempo(microsecs)
|
147
|
-
end
|
148
|
-
|
149
|
-
def key_signature(sharpflat, is_minor)
|
150
|
-
end
|
151
|
-
|
152
|
-
def arbitrary(msg)
|
153
|
-
end
|
154
|
-
|
155
|
-
# End of NOPs.
|
156
|
-
|
157
|
-
|
158
|
-
# Read through 'MThd' or 'MTrk' header string. If skip is true, attempt
|
159
|
-
# to skip initial trash. If there is an error, #error is called.
|
160
|
-
def read_mt_header_string(bytes, skip)
|
161
|
-
b = []
|
162
|
-
bytes_to_read = 4
|
163
|
-
while true
|
164
|
-
data = get_bytes(bytes_to_read)
|
165
|
-
b += data
|
166
|
-
if b.length < 4
|
167
|
-
error("unexpected EOF while trying to read header" +
|
168
|
-
" string #{s}")
|
169
|
-
end
|
170
|
-
|
171
|
-
# See if we found the bytes we're looking for
|
172
|
-
return if b == bytes
|
173
|
-
|
174
|
-
if skip # Try again with the next char
|
175
|
-
i = b[1..-1].index(bytes[0])
|
176
|
-
if i.nil?
|
177
|
-
b = []
|
178
|
-
bytes_to_read = 4
|
179
|
-
else
|
180
|
-
b = b[i..-1]
|
181
|
-
bytes_to_read = 4 - i
|
182
|
-
end
|
183
|
-
else
|
184
|
-
error("header string #{bytes.collect{|b| b.chr}.join} not found")
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
# Read a header chunk.
|
190
|
-
def read_header
|
191
|
-
@bytes_to_be_read = 0
|
192
|
-
read_mt_header_string(MThd_BYTE_ARRAY, @skip_init) # "MThd"
|
193
|
-
|
194
|
-
@bytes_to_be_read = read32()
|
195
|
-
format = read16()
|
196
|
-
ntrks = read16()
|
197
|
-
division = read16()
|
198
|
-
|
199
|
-
header(format, ntrks, division)
|
26
|
+
# Raw data info
|
27
|
+
attr_accessor :raw_time_stamp_data
|
200
28
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
29
|
+
def initialize
|
30
|
+
@no_merge = false
|
31
|
+
@skip_init = true
|
32
|
+
@io = nil
|
33
|
+
@bytes_to_be_read = 0
|
34
|
+
@msg_buf = nil
|
35
|
+
end
|
206
36
|
|
207
|
-
|
208
|
-
|
37
|
+
# The only public method. Each MIDI event in the file causes a
|
38
|
+
# method to be called.
|
39
|
+
def read_from(io)
|
40
|
+
error('must specify non-nil input stream') if io.nil?
|
41
|
+
@io = io
|
209
42
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
if !sysex_continue
|
281
|
-
handle_arbitrary(msg())
|
282
|
-
elsif c == EOX
|
283
|
-
handle_sysex(msg())
|
284
|
-
sysex_continue = false
|
285
|
-
end
|
286
|
-
else
|
287
|
-
bad_byte(c)
|
288
|
-
end
|
289
|
-
end
|
290
|
-
end_track()
|
291
|
-
end
|
43
|
+
ntrks = read_header
|
44
|
+
error('No tracks!') if ntrks <= 0
|
45
|
+
|
46
|
+
ntrks.times { read_track }
|
47
|
+
end
|
48
|
+
|
49
|
+
# This default getc implementation tries to read a single byte
|
50
|
+
# from io and returns it as an integer.
|
51
|
+
def getc
|
52
|
+
@bytes_to_be_read -= 1
|
53
|
+
@io.readbyte
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the next +n+ bytes from @io as an array.
|
57
|
+
def get_bytes(n)
|
58
|
+
buf = []
|
59
|
+
n.times { buf << getc }
|
60
|
+
buf
|
61
|
+
end
|
62
|
+
|
63
|
+
# The default error handler.
|
64
|
+
def error(str)
|
65
|
+
loc = @io.tell - 1
|
66
|
+
raise "#{self.class.name} error at byte #{loc} (0x#{'%02x' % loc}): #{str}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# The rest of these are NOPs by default.
|
70
|
+
|
71
|
+
# MIDI header.
|
72
|
+
def header(format, ntrks, division)
|
73
|
+
end
|
74
|
+
|
75
|
+
def start_track(bytes_to_be_read)
|
76
|
+
end
|
77
|
+
|
78
|
+
def end_track
|
79
|
+
end
|
80
|
+
|
81
|
+
def note_on(chan, note, vel)
|
82
|
+
end
|
83
|
+
|
84
|
+
def note_off(chan, note, vel)
|
85
|
+
end
|
86
|
+
|
87
|
+
def pressure(chan, note, press)
|
88
|
+
end
|
89
|
+
|
90
|
+
def controller(chan, control, value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def pitch_bend(chan, msb, lsb)
|
94
|
+
end
|
95
|
+
|
96
|
+
def program(chan, program)
|
97
|
+
end
|
98
|
+
|
99
|
+
def chan_pressure(chan, press)
|
100
|
+
end
|
101
|
+
|
102
|
+
def sysex(msg)
|
103
|
+
end
|
104
|
+
|
105
|
+
def meta_misc(type, msg)
|
106
|
+
end
|
107
|
+
|
108
|
+
def sequencer_specific(type, msg)
|
109
|
+
end
|
110
|
+
|
111
|
+
def sequence_number(num)
|
112
|
+
end
|
292
113
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
114
|
+
def text(type, msg)
|
115
|
+
end
|
116
|
+
|
117
|
+
def eot
|
118
|
+
end
|
297
119
|
|
298
|
-
|
299
|
-
|
300
|
-
m = msg() # Copy of internal message buffer
|
301
|
-
|
302
|
-
# Create raw data array
|
303
|
-
@raw_data = []
|
304
|
-
@raw_data << META_EVENT
|
305
|
-
@raw_data << type
|
306
|
-
@raw_data << @raw_var_num_data
|
307
|
-
@raw_data << m
|
308
|
-
@raw_data.flatten!
|
309
|
-
|
310
|
-
case type
|
311
|
-
when META_SEQ_NUM
|
312
|
-
sequence_number((m[0] << 8) + m[1])
|
313
|
-
when META_TEXT, META_COPYRIGHT, META_SEQ_NAME, META_INSTRUMENT,
|
314
|
-
META_LYRIC, META_MARKER, META_CUE, 0x08, 0x09, 0x0a,
|
315
|
-
0x0b, 0x0c, 0x0d, 0x0e, 0x0f
|
316
|
-
text(type, m)
|
317
|
-
when META_TRACK_END
|
318
|
-
eot()
|
319
|
-
when META_SET_TEMPO
|
320
|
-
tempo((m[0] << 16) + (m[1] << 8) + m[2])
|
321
|
-
when META_SMPTE
|
322
|
-
smpte(m[0], m[1], m[2], m[3], m[4])
|
323
|
-
when META_TIME_SIG
|
324
|
-
time_signature(m[0], m[1], m[2], m[3])
|
325
|
-
when META_KEY_SIG
|
326
|
-
key_signature(m[0], m[1] == 0 ? false : true)
|
327
|
-
when META_SEQ_SPECIF
|
328
|
-
sequencer_specific(type, m)
|
329
|
-
else
|
330
|
-
meta_misc(type, m)
|
331
|
-
end
|
332
|
-
end
|
120
|
+
def time_signature(numer, denom, clocks, qnotes)
|
121
|
+
end
|
333
122
|
|
334
|
-
|
335
|
-
|
336
|
-
@raw_data = []
|
337
|
-
@raw_data << status unless running
|
338
|
-
@raw_data << c1
|
339
|
-
@raw_data << c2
|
340
|
-
|
341
|
-
chan = status & 0x0f
|
342
|
-
|
343
|
-
case (status & 0xf0)
|
344
|
-
when NOTE_OFF
|
345
|
-
note_off(chan, c1, c2)
|
346
|
-
when NOTE_ON
|
347
|
-
note_on(chan, c1, c2)
|
348
|
-
when POLY_PRESSURE
|
349
|
-
pressure(chan, c1, c2)
|
350
|
-
when CONTROLLER
|
351
|
-
controller(chan, c1, c2)
|
352
|
-
when PITCH_BEND
|
353
|
-
pitch_bend(chan, c1, c2)
|
354
|
-
when PROGRAM_CHANGE
|
355
|
-
program(chan, c1)
|
356
|
-
when CHANNEL_PRESSURE
|
357
|
-
chan_pressure(chan, c1)
|
358
|
-
else
|
359
|
-
error("illegal chan message 0x#{'%02x' % (status & 0xf0)}\n")
|
360
|
-
end
|
361
|
-
end
|
123
|
+
def smpte(hour, min, sec, frame, fract)
|
124
|
+
end
|
362
125
|
|
363
|
-
|
364
|
-
|
365
|
-
@raw_data = msg.dup()
|
366
|
-
sysex(msg)
|
367
|
-
end
|
126
|
+
def tempo(microsecs)
|
127
|
+
end
|
368
128
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
129
|
+
def key_signature(sharpflat, is_minor)
|
130
|
+
end
|
131
|
+
|
132
|
+
def arbitrary(msg)
|
133
|
+
end
|
374
134
|
|
375
|
-
|
376
|
-
def read16
|
377
|
-
val = (getc() << 8) + getc()
|
378
|
-
val = -(val & 0x7fff) if (val & 0x8000).nonzero?
|
379
|
-
return val
|
380
|
-
end
|
135
|
+
# End of NOPs.
|
381
136
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
137
|
+
# Read through 'MThd' or 'MTrk' header string. If skip is true, attempt
|
138
|
+
# to skip initial trash. If there is an error, #error is called.
|
139
|
+
def read_mt_header_string(bytes, skip)
|
140
|
+
b = []
|
141
|
+
bytes_to_read = 4
|
142
|
+
while true
|
143
|
+
data = get_bytes(bytes_to_read)
|
144
|
+
b += data
|
145
|
+
error("unexpected EOF while trying to read header string #{s}") if b.length < 4
|
146
|
+
|
147
|
+
# See if we found the bytes we're looking for
|
148
|
+
return if b == bytes
|
149
|
+
|
150
|
+
if skip # Try again with the next char
|
151
|
+
i = b[1..-1].index(bytes[0])
|
152
|
+
if i.nil?
|
153
|
+
b = []
|
154
|
+
bytes_to_read = 4
|
155
|
+
else
|
156
|
+
b = b[i..-1]
|
157
|
+
bytes_to_read = 4 - i
|
158
|
+
end
|
159
|
+
else
|
160
|
+
error("header string #{bytes.collect { |b| b.chr }.join} not found")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Read a header chunk.
|
166
|
+
def read_header
|
167
|
+
@bytes_to_be_read = 0
|
168
|
+
read_mt_header_string(MThd_BYTE_ARRAY, @skip_init) # "MThd"
|
169
|
+
|
170
|
+
@bytes_to_be_read = read32
|
171
|
+
format = read16
|
172
|
+
ntrks = read16
|
173
|
+
division = read16
|
174
|
+
|
175
|
+
header(format, ntrks, division)
|
176
|
+
|
177
|
+
# Flush any extra stuff, in case the length of the header is not 6
|
178
|
+
if @bytes_to_be_read > 0
|
179
|
+
get_bytes(@bytes_to_be_read)
|
180
|
+
@bytes_to_be_read = 0
|
181
|
+
end
|
182
|
+
|
183
|
+
ntrks
|
184
|
+
end
|
185
|
+
|
186
|
+
# Read a track chunk.
|
187
|
+
def read_track
|
188
|
+
c = c1 = type = needed = 0
|
189
|
+
sysex_continue = false # True if last msg was unfinished
|
190
|
+
running = false # True when running status used
|
191
|
+
status = 0 # (Possibly running) status byte
|
192
|
+
|
193
|
+
@bytes_to_be_read = 0
|
194
|
+
read_mt_header_string(MTrk_BYTE_ARRAY, false)
|
195
|
+
|
196
|
+
@bytes_to_be_read = read32
|
197
|
+
@curr_ticks = @ticks_so_far = 0
|
198
|
+
|
199
|
+
start_track
|
200
|
+
|
201
|
+
while @bytes_to_be_read > 0
|
202
|
+
@curr_ticks = read_var_len # Delta time
|
203
|
+
@ticks_so_far += @curr_ticks
|
204
|
+
|
205
|
+
# Copy raw var num data into raw time stamp data
|
206
|
+
@raw_time_stamp_data = @raw_var_num_data.dup
|
207
|
+
|
208
|
+
c = getc # Read first byte
|
209
|
+
|
210
|
+
error("didn't find expected continuation of a sysex") if sysex_continue && c != EOX
|
211
|
+
|
212
|
+
if (c & 0x80).zero? # Running status?
|
213
|
+
error('unexpected running status') if status.zero?
|
214
|
+
running = true
|
215
|
+
else
|
216
|
+
status = c
|
217
|
+
running = false
|
218
|
+
end
|
219
|
+
|
220
|
+
needed = NUM_DATA_BYTES[(status >> 4) & 0x0f]
|
221
|
+
|
222
|
+
if needed.nonzero? # i.e., is it a channel message?
|
223
|
+
c1 = running ? c : (getc & 0x7f)
|
224
|
+
|
225
|
+
# The "& 0x7f" here may seem unnecessary, but I've seen
|
226
|
+
# "bad" MIDI files that had, for example, volume bytes
|
227
|
+
# with the upper bit set. This code should not harm
|
228
|
+
# proper data.
|
229
|
+
chan_message(running, status, c1,
|
230
|
+
needed > 1 ? (getc & 0x7f) : 0)
|
231
|
+
next
|
232
|
+
end
|
233
|
+
|
234
|
+
case c
|
235
|
+
when META_EVENT # Meta event
|
236
|
+
type = getc
|
237
|
+
msg_init
|
238
|
+
msg_read(read_var_len)
|
239
|
+
meta_event(type)
|
240
|
+
when SYSEX # Start of system exclusive
|
241
|
+
msg_init
|
242
|
+
msg_add(SYSEX)
|
243
|
+
c = msg_read(read_var_len)
|
244
|
+
|
245
|
+
if c == EOX || !@no_merge
|
246
|
+
handle_sysex(msg)
|
247
|
+
else
|
248
|
+
sysex_continue = true
|
249
|
+
end
|
250
|
+
when EOX # Sysex continuation or arbitrary stuff
|
251
|
+
msg_init unless sysex_continue
|
252
|
+
c = msg_read(read_var_len)
|
253
|
+
|
254
|
+
if !sysex_continue
|
255
|
+
handle_arbitrary(msg)
|
256
|
+
elsif c == EOX
|
257
|
+
handle_sysex(msg)
|
258
|
+
sysex_continue = false
|
259
|
+
end
|
260
|
+
else
|
261
|
+
bad_byte(c)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end_track
|
265
|
+
end
|
266
|
+
|
267
|
+
# Handle an unexpected byte.
|
268
|
+
def bad_byte(c)
|
269
|
+
error(format('unexpected byte: 0x%02x', c))
|
270
|
+
end
|
271
|
+
|
272
|
+
# Handle a meta event.
|
273
|
+
def meta_event(type)
|
274
|
+
m = msg # Copy of internal message buffer
|
275
|
+
|
276
|
+
# Create raw data array
|
277
|
+
@raw_data = []
|
278
|
+
@raw_data << META_EVENT
|
279
|
+
@raw_data << type
|
280
|
+
@raw_data << @raw_var_num_data
|
281
|
+
@raw_data << m
|
282
|
+
@raw_data.flatten!
|
283
|
+
|
284
|
+
case type
|
285
|
+
when META_SEQ_NUM
|
286
|
+
sequence_number((m[0] << 8) + m[1])
|
287
|
+
when META_TEXT, META_COPYRIGHT, META_SEQ_NAME, META_INSTRUMENT,
|
288
|
+
META_LYRIC, META_MARKER, META_CUE, 0x08, 0x09, 0x0a,
|
289
|
+
0x0b, 0x0c, 0x0d, 0x0e, 0x0f
|
290
|
+
text(type, m)
|
291
|
+
when META_TRACK_END
|
292
|
+
eot
|
293
|
+
when META_SET_TEMPO
|
294
|
+
tempo((m[0] << 16) + (m[1] << 8) + m[2])
|
295
|
+
when META_SMPTE
|
296
|
+
smpte(m[0], m[1], m[2], m[3], m[4])
|
297
|
+
when META_TIME_SIG
|
298
|
+
time_signature(m[0], m[1], m[2], m[3])
|
299
|
+
when META_KEY_SIG
|
300
|
+
key_signature(m[0], !(m[1] == 0))
|
301
|
+
when META_SEQ_SPECIF
|
302
|
+
sequencer_specific(type, m)
|
303
|
+
else
|
304
|
+
meta_misc(type, m)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Handle a channel message (note on, note off, etc.)
|
309
|
+
def chan_message(running, status, c1, c2)
|
310
|
+
@raw_data = []
|
311
|
+
@raw_data << status unless running
|
312
|
+
@raw_data << c1
|
313
|
+
@raw_data << c2
|
314
|
+
|
315
|
+
chan = status & 0x0f
|
316
|
+
|
317
|
+
case (status & 0xf0)
|
318
|
+
when NOTE_OFF
|
319
|
+
note_off(chan, c1, c2)
|
320
|
+
when NOTE_ON
|
321
|
+
note_on(chan, c1, c2)
|
322
|
+
when POLY_PRESSURE
|
323
|
+
pressure(chan, c1, c2)
|
324
|
+
when CONTROLLER
|
325
|
+
controller(chan, c1, c2)
|
326
|
+
when PITCH_BEND
|
327
|
+
pitch_bend(chan, c1, c2)
|
328
|
+
when PROGRAM_CHANGE
|
329
|
+
program(chan, c1)
|
330
|
+
when CHANNEL_PRESSURE
|
331
|
+
chan_pressure(chan, c1)
|
332
|
+
else
|
333
|
+
error("illegal chan message 0x#{format('%02x', (status & 0xf0))}\n")
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Copy message into raw data array, then call sysex().
|
338
|
+
def handle_sysex(msg)
|
339
|
+
@raw_data = msg.dup
|
340
|
+
sysex(msg)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Copy message into raw data array, then call arbitrary().
|
344
|
+
def handle_arbitrary(msg)
|
345
|
+
@raw_data = msg.dup
|
346
|
+
arbitrary(msg)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Read and return a sixteen bit value.
|
350
|
+
def read16
|
351
|
+
val = (getc << 8) + getc
|
352
|
+
val = -(val & 0x7fff) if (val & 0x8000).nonzero?
|
353
|
+
val
|
354
|
+
end
|
355
|
+
|
356
|
+
# Read and return a 32-bit value.
|
357
|
+
def read32
|
358
|
+
val = (getc << 24) + (getc << 16) + (getc << 8) +
|
359
|
+
getc
|
360
|
+
val = -(val & 0x7fffffff) if (val & 0x80000000).nonzero?
|
361
|
+
val
|
362
|
+
end
|
363
|
+
|
364
|
+
# Read a varlen value.
|
365
|
+
def read_var_len
|
366
|
+
@raw_var_num_data = []
|
367
|
+
c = getc
|
368
|
+
@raw_var_num_data << c
|
369
|
+
val = c
|
370
|
+
if (val & 0x80).nonzero?
|
371
|
+
val &= 0x7f
|
372
|
+
while true
|
373
|
+
c = getc
|
374
|
+
@raw_var_num_data << c
|
375
|
+
val = (val << 7) + (c & 0x7f)
|
376
|
+
break if (c & 0x80).zero?
|
377
|
+
end
|
378
|
+
end
|
379
|
+
val
|
380
|
+
end
|
381
|
+
|
382
|
+
# Write a sixteen-bit value.
|
383
|
+
def write16(val)
|
384
|
+
val = (-val) | 0x8000 if val < 0
|
385
|
+
putc((val >> 8) & 0xff)
|
386
|
+
putc(val & 0xff)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Write a 32-bit value.
|
390
|
+
def write32(val)
|
391
|
+
val = (-val) | 0x80000000 if val < 0
|
392
|
+
putc((val >> 24) & 0xff)
|
393
|
+
putc((val >> 16) & 0xff)
|
394
|
+
putc((val >> 8) & 0xff)
|
395
|
+
putc(val & 0xff)
|
396
|
+
end
|
397
|
+
|
398
|
+
# Write a variable length value.
|
399
|
+
def write_var_len(val)
|
400
|
+
if val.zero?
|
401
|
+
putc(0)
|
402
|
+
return
|
403
|
+
end
|
404
|
+
|
405
|
+
buf = []
|
406
|
+
|
407
|
+
buf << (val & 0x7f)
|
408
|
+
while (value >>= 7) > 0
|
409
|
+
buf << (val & 0x7f) | 0x80
|
410
|
+
end
|
411
|
+
|
412
|
+
buf.reverse.each { |b| putc(b) }
|
413
|
+
end
|
414
|
+
|
415
|
+
# Add a byte to the current message buffer.
|
416
|
+
def msg_add(c)
|
417
|
+
@msg_buf << c
|
418
|
+
end
|
419
|
+
|
420
|
+
# Read and add a number of bytes to the message buffer. Return
|
421
|
+
# the last byte (so we can see if it's an EOX or not).
|
422
|
+
def msg_read(n_bytes)
|
423
|
+
@msg_buf += get_bytes(n_bytes)
|
424
|
+
@msg_buf.flatten!
|
425
|
+
@msg_buf[-1]
|
426
|
+
end
|
427
|
+
|
428
|
+
# Initialize the internal message buffer.
|
429
|
+
def msg_init
|
430
|
+
@msg_buf = []
|
431
|
+
end
|
432
|
+
|
433
|
+
# Return a copy of the internal message buffer.
|
434
|
+
def msg
|
435
|
+
@msg_buf.dup
|
436
|
+
end
|
388
437
|
end
|
389
|
-
|
390
|
-
# Read a varlen value.
|
391
|
-
def read_var_len
|
392
|
-
@raw_var_num_data = []
|
393
|
-
c = getc()
|
394
|
-
@raw_var_num_data << c
|
395
|
-
val = c
|
396
|
-
if (val & 0x80).nonzero?
|
397
|
-
val &= 0x7f
|
398
|
-
while true
|
399
|
-
c = getc()
|
400
|
-
@raw_var_num_data << c
|
401
|
-
val = (val << 7) + (c & 0x7f)
|
402
|
-
break if (c & 0x80).zero?
|
403
|
-
end
|
404
|
-
end
|
405
|
-
return val
|
406
|
-
end
|
407
|
-
|
408
|
-
# Write a sixteen-bit value.
|
409
|
-
def write16(val)
|
410
|
-
val = (-val) | 0x8000 if val < 0
|
411
|
-
putc((val >> 8) & 0xff)
|
412
|
-
putc(val & 0xff)
|
413
|
-
end
|
414
|
-
|
415
|
-
# Write a 32-bit value.
|
416
|
-
def write32(val)
|
417
|
-
val = (-val) | 0x80000000 if val < 0
|
418
|
-
putc((val >> 24) & 0xff)
|
419
|
-
putc((val >> 16) & 0xff)
|
420
|
-
putc((val >> 8) & 0xff)
|
421
|
-
putc(val & 0xff)
|
422
|
-
end
|
423
|
-
|
424
|
-
# Write a variable length value.
|
425
|
-
def write_var_len(val)
|
426
|
-
if val.zero?
|
427
|
-
putc(0)
|
428
|
-
return
|
429
|
-
end
|
430
|
-
|
431
|
-
buf = []
|
432
|
-
|
433
|
-
buf << (val & 0x7f)
|
434
|
-
while (value >>= 7) > 0
|
435
|
-
buf << (val & 0x7f) | 0x80
|
436
|
-
end
|
437
|
-
|
438
|
-
buf.reverse.each { | b | putc(b) }
|
439
|
-
end
|
440
|
-
|
441
|
-
# Add a byte to the current message buffer.
|
442
|
-
def msg_add(c)
|
443
|
-
@msg_buf << c
|
444
|
-
end
|
445
|
-
|
446
|
-
# Read and add a number of bytes to the message buffer. Return
|
447
|
-
# the last byte (so we can see if it's an EOX or not).
|
448
|
-
def msg_read(n_bytes)
|
449
|
-
@msg_buf += get_bytes(n_bytes)
|
450
|
-
@msg_buf.flatten!
|
451
|
-
return @msg_buf[-1]
|
452
|
-
end
|
453
|
-
|
454
|
-
# Initialize the internal message buffer.
|
455
|
-
def msg_init
|
456
|
-
@msg_buf = []
|
457
|
-
end
|
458
|
-
|
459
|
-
# Return a copy of the internal message buffer.
|
460
|
-
def msg
|
461
|
-
return @msg_buf.dup()
|
462
|
-
end
|
463
|
-
|
464
|
-
end
|
465
|
-
|
466
|
-
end
|
438
|
+
end
|
467
439
|
end
|