Class: MIDI::IO::MIDIFile

Inherits:
Object
  • Object
show all
Defined in:
lib/midilib/io/midifile.rb

Overview

A MIDIFile parses a MIDI file and calls methods when it sees MIDI events. Most of the methods are stubs. To do anything interesting with the events, override these methods (those between the “The rest of these are NOPs by default” and “End of NOPs” comments).

See SeqReader for a subclass that uses these methods to create Event objects.

Direct Known Subclasses

SeqReader

Constant Summary collapse

NUM_DATA_BYTES =

This array is indexed by the high half of a status byte. Its value is either the number of bytes needed (1 or 2) for a channel message, or 0 if it’s not a channel message.

[
	0, 0, 0, 0, 0, 0, 0, 0, # 0x00 - 0x70
	2, 2, 2, 2, 1, 1, 2, 0  # 0x80 - 0xf0
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeMIDIFile

Returns a new instance of MIDIFile.



36
37
38
39
40
41
42
# File 'lib/midilib/io/midifile.rb', line 36

def initialize
	@no_merge = false
	@skip_init = true
	@io = nil
	@bytes_to_be_read = 0
	@msg_buf = nil
end

Instance Attribute Details

#bytes_to_be_readObject

Counts number of bytes expected



26
27
28
# File 'lib/midilib/io/midifile.rb', line 26

def bytes_to_be_read
  @bytes_to_be_read
end

#curr_ticksObject

Current time, from delta-time in MIDI file



24
25
26
# File 'lib/midilib/io/midifile.rb', line 24

def curr_ticks
  @curr_ticks
end

#no_mergeObject

true means continued sysex are not collapsed



28
29
30
# File 'lib/midilib/io/midifile.rb', line 28

def no_merge
  @no_merge
end

#raw_dataObject

Returns the value of attribute raw_data.



34
35
36
# File 'lib/midilib/io/midifile.rb', line 34

def raw_data
  @raw_data
end

#raw_time_stamp_dataObject

Raw data info



32
33
34
# File 'lib/midilib/io/midifile.rb', line 32

def raw_time_stamp_data
  @raw_time_stamp_data
end

#raw_var_num_dataObject

Returns the value of attribute raw_var_num_data.



33
34
35
# File 'lib/midilib/io/midifile.rb', line 33

def raw_var_num_data
  @raw_var_num_data
end

#skip_initObject

true if initial garbage should be skipped



29
30
31
# File 'lib/midilib/io/midifile.rb', line 29

def skip_init
  @skip_init
end

#ticks_so_farObject

Number of delta-time ticks so far



25
26
27
# File 'lib/midilib/io/midifile.rb', line 25

def ticks_so_far
  @ticks_so_far
end

Instance Method Details

#arbitrary(msg) ⇒ Object



132
133
# File 'lib/midilib/io/midifile.rb', line 132

def arbitrary(msg)
end

#bad_byte(c) ⇒ Object

Handle an unexpected byte.



274
275
276
# File 'lib/midilib/io/midifile.rb', line 274

def bad_byte(c)
	error(sprintf("unexpected byte: 0x%02x", c))
end

#chan_message(running, status, c1, c2) ⇒ Object

Handle a channel message (note on, note off, etc.)



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/midilib/io/midifile.rb', line 314

def chan_message(running, status, c1, c2)
	@raw_data = []
	@raw_data << status unless running
	@raw_data << c1
	@raw_data << c2

	chan = status & 0x0f

	case (status & 0xf0)
	when NOTE_OFF
 note_off(chan, c1, c2)
	when NOTE_ON
 note_on(chan, c1, c2)
	when POLY_PRESSURE
 pressure(chan, c1, c2)
	when CONTROLLER
 controller(chan, c1, c2)
	when PITCH_BEND
 pitch_bend(chan, c1, c2)
	when PROGRAM_CHANGE
 program(chan, c1)
	when CHANNEL_PRESSURE
 chan_pressure(chan, c1)
	else
 error("illegal chan message 0x#{'%02x' % (status & 0xf0)}\n")
	end
end

#chan_pressure(chan, press) ⇒ Object



99
100
# File 'lib/midilib/io/midifile.rb', line 99

def chan_pressure(chan, press)
end

#controller(chan, control, value) ⇒ Object



90
91
# File 'lib/midilib/io/midifile.rb', line 90

def controller(chan, control, value)
end

#end_trackObject



78
79
# File 'lib/midilib/io/midifile.rb', line 78

def end_track()
end

#eotObject



117
118
# File 'lib/midilib/io/midifile.rb', line 117

def eot()
end

#error(str) ⇒ Object

The default error handler.



64
65
66
67
# File 'lib/midilib/io/midifile.rb', line 64

def error(str)
	loc = @io.tell() - 1
	raise "#{self.class.name} error at byte #{loc} (0x#{'%02x' % loc}): #{str}"
end

#getcObject

This default getc implementation tries to read a single character from io.



58
59
60
61
# File 'lib/midilib/io/midifile.rb', line 58

def getc
	@bytes_to_be_read -= 1
	return @io.getc()
end

#handle_arbitrary(msg) ⇒ Object

Copy message into raw data array, then call arbitrary().



349
350
351
352
# File 'lib/midilib/io/midifile.rb', line 349

def handle_arbitrary(msg)
	@raw_data = msg.dup()
	arbitrary(msg)
end

#handle_sysex(msg) ⇒ Object

Copy message into raw data array, then call sysex().



343
344
345
346
# File 'lib/midilib/io/midifile.rb', line 343

def handle_sysex(msg)
	@raw_data = msg.dup()
	sysex(msg)
end

#header(format, ntrks, division) ⇒ Object

MIDI header.



72
73
# File 'lib/midilib/io/midifile.rb', line 72

def header(format, ntrks, division)
end

#key_signature(sharpflat, is_minor) ⇒ Object



129
130
# File 'lib/midilib/io/midifile.rb', line 129

def key_signature(sharpflat, is_minor)
end

#meta_event(type) ⇒ Object

Handle a meta event.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/midilib/io/midifile.rb', line 279

def meta_event(type)
	m = msg()		# Copy of internal message buffer

	# Create raw data array
	@raw_data = ''
	@raw_data << META_EVENT
	@raw_data << type
	@raw_data << @raw_var_num_data
	@raw_data << m

	case type
	when META_SEQ_NUM
 sequence_number((m[0] << 8) + m[1])
	when META_TEXT, META_COPYRIGHT, META_SEQ_NAME, META_INSTRUMENT,
		META_LYRIC, META_MARKER, META_CUE, 0x08, 0x09, 0x0a,
		0x0b, 0x0c, 0x0d, 0x0e, 0x0f
 text(type, m)
	when META_TRACK_END
 eot()
	when META_SET_TEMPO
 tempo((m[0] << 16) + (m[1] << 8) + m[2])
	when META_SMPTE
 smpte(m[0], m[1], m[2], m[3], m[4])
	when META_TIME_SIG
 time_signature(m[0], m[1], m[2], m[3])
	when META_KEY_SIG
 key_signature(m[0], m[1] == 0 ? false : true)
	when META_SEQ_SPECIF
 sequencer_specific(type, m)
	else
 meta_misc(type, m)
	end
end

#meta_misc(type, msg) ⇒ Object



105
106
# File 'lib/midilib/io/midifile.rb', line 105

def meta_misc(type, msg)
end

#msgObject

Return a copy of the internal message buffer.



439
440
441
# File 'lib/midilib/io/midifile.rb', line 439

def msg
	return @msg_buf.dup()
end

#msg_add(c) ⇒ Object

Add a byte to the current message buffer.



421
422
423
# File 'lib/midilib/io/midifile.rb', line 421

def msg_add(c)
	@msg_buf << c
end

#msg_initObject

Initialize the internal message buffer.



434
435
436
# File 'lib/midilib/io/midifile.rb', line 434

def msg_init
	@msg_buf = ''
end

#msg_read(n_bytes) ⇒ Object

Read and add a number of bytes to the message buffer. Return the last byte (so we can see if it’s an EOX or not).



427
428
429
430
431
# File 'lib/midilib/io/midifile.rb', line 427

def msg_read(n_bytes)
	@msg_buf << @io.read(n_bytes)
	@bytes_to_be_read -= n_bytes
	return @msg_buf[-1]
end

#note_off(chan, note, vel) ⇒ Object



84
85
# File 'lib/midilib/io/midifile.rb', line 84

def note_off(chan, note, vel)
end

#note_on(chan, note, vel) ⇒ Object



81
82
# File 'lib/midilib/io/midifile.rb', line 81

def note_on(chan, note, vel)
end

#pitch_bend(chan, msb, lsb) ⇒ Object



93
94
# File 'lib/midilib/io/midifile.rb', line 93

def pitch_bend(chan, msb, lsb)
end

#pressure(chan, note, press) ⇒ Object



87
88
# File 'lib/midilib/io/midifile.rb', line 87

def pressure(chan, note, press)
end

#program(chan, program) ⇒ Object



96
97
# File 'lib/midilib/io/midifile.rb', line 96

def program(chan, program)
end

#read16Object

Read and return a sixteen bit value.



355
356
357
358
359
# File 'lib/midilib/io/midifile.rb', line 355

def read16
	val = (getc() << 8) + getc()
	val = -(val & 0x7fff) if (val & 0x8000).nonzero?
	return val
end

#read32Object

Read and return a 32-bit value.



362
363
364
365
366
367
# File 'lib/midilib/io/midifile.rb', line 362

def read32
	val = (getc() << 24) + (getc() << 16) + (getc() << 8) +
 getc()
	val = -(val & 0x7fffffff) if (val & 0x80000000).nonzero?
	return val
end

#read_from(io) ⇒ Object

The only public method. Each MIDI event in the file causes a method to be called.



46
47
48
49
50
51
52
53
54
# File 'lib/midilib/io/midifile.rb', line 46

def read_from(io)
	error('must specify non-nil input stream') if io.nil?
	@io = io

	ntrks = read_header()
	error('No tracks!') if ntrks <= 0

	ntrks.times { read_track() }
end

#read_headerObject

Read a header chunk.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/midilib/io/midifile.rb', line 170

def read_header
	@bytes_to_be_read = 0
	read_mt_header_string('MThd', @skip_init)

	@bytes_to_be_read = read32()
	format = read16()
	ntrks = read16()
	division = read16()

	header(format, ntrks, division)

	# Flush any extra stuff, in case the length of the header is not 6
	if @bytes_to_be_read > 0
 @io.read(@bytes_to_be_read)
 @bytes_to_be_read = 0
	end

	return ntrks
end

#read_mt_header_string(s, skip) ⇒ Object

Read through ‘MThd’ or ‘MTrk’ header string. If skip is true, attempt to skip initial trash. If there is an error, #error is called.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/midilib/io/midifile.rb', line 140

def read_mt_header_string(s, skip)
	b = ''
	bytes_to_read = 4
	while true
 b << @io.read(bytes_to_read)
 if b.length < 4
		error("unexpected EOF while trying to read header" +
    " string #{s}")
 end
 @bytes_to_be_read -= bytes_to_read

 # See if we found the string we're looking for
 return if b == s

 if skip		# Try again with the next char
		i = b.index(s[0], 1)
		if i.nil?
  b = ''
  bytes_to_read = 4
		else
  b = b[i..-1]
  bytes_to_read = 4 - i
		end
 else
		error("header string #{s} not found")
 end
	end
end

#read_trackObject

Read a track chunk.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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
# File 'lib/midilib/io/midifile.rb', line 191

def read_track
	c = c1 = type = needed = 0
	sysex_continue = false	# True if last msg was unfinished
	running = false		# True when running status used
	status = 0		# (Possibly running) status byte

	@bytes_to_be_read = 0
	read_mt_header_string('MTrk', false)

	@bytes_to_be_read = read32()
	@curr_ticks = @ticks_so_far = 0

	start_track()

	while @bytes_to_be_read > 0
 @curr_ticks = read_var_len() # Delta time
 @ticks_so_far += @curr_ticks

 # Copy raw var num data into raw time stamp data
 @raw_time_stamp_data = @raw_var_num_data.dup()

 c = getc()		# Read first byte

 if sysex_continue && c != EOX
		error("didn't find expected continuation of a sysex")
 end

 if (c & 0x80).zero? # Running status?
		error('unexpected running status') if status.zero?
		running = true
 else
		status = c
		running = false
 end

 needed = NUM_DATA_BYTES[(status >> 4) & 0x0f]

 if needed.nonzero?	# i.e., is it a channel message?
		c1 = running ? c : (getc() & 0x7f)

		# The "& 0x7f" here may seem unnecessary, but I've seen
		# "bad" MIDI files that had, for example, volume bytes
		# with the upper bit set. This code should not harm
		# proper data.
		chan_message(running, status, c1,
   (needed > 1) ? (getc() & 0x7f) : 0)
		next
 end

 case c
 when META_EVENT	# Meta event
		type = getc()
		msg_init()
		msg_read(read_var_len())
		meta_event(type)
 when SYSEX		# Start of system exclusive
		msg_init()
		msg_add(SYSEX)
		c = msg_read(read_var_len())

		if c == EOX || !@no_merge
  handle_sysex(msg())
		else
  sysex_continue = true
		end
 when EOX		# Sysex continuation or arbitrary stuff
		msg_init() if !sysex_continue
		c = msg_read(read_var_len())

		if !sysex_continue
  handle_arbitrary(msg())
		elsif c == EOX
  handle_sysex(msg())
  sysex_continue = false
		end
 else
		bad_byte(c)
 end
	end
	end_track()
end

#read_var_lenObject

Read a varlen value.



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/midilib/io/midifile.rb', line 370

def read_var_len
	@raw_var_num_data = ''
	c = getc()
	@raw_var_num_data << c
	val = c
	if (val & 0x80).nonzero?
 val &= 0x7f
 while true
		c = getc()
		@raw_var_num_data << c
		val = (val << 7) + (c & 0x7f)
		break if (c & 0x80).zero?
 end
	end
	return val
end

#sequence_number(num) ⇒ Object



111
112
# File 'lib/midilib/io/midifile.rb', line 111

def sequence_number(num)
end

#sequencer_specific(type, msg) ⇒ Object



108
109
# File 'lib/midilib/io/midifile.rb', line 108

def sequencer_specific(type, msg)
end

#smpte(hour, min, sec, frame, fract) ⇒ Object



123
124
# File 'lib/midilib/io/midifile.rb', line 123

def smpte(hour, min, sec, frame, fract)
end

#start_track(bytes_to_be_read) ⇒ Object



75
76
# File 'lib/midilib/io/midifile.rb', line 75

def start_track(bytes_to_be_read)
end

#sysex(msg) ⇒ Object



102
103
# File 'lib/midilib/io/midifile.rb', line 102

def sysex(msg)
end

#tempo(microsecs) ⇒ Object



126
127
# File 'lib/midilib/io/midifile.rb', line 126

def tempo(microsecs)
end

#text(type, msg) ⇒ Object



114
115
# File 'lib/midilib/io/midifile.rb', line 114

def text(type, msg)
end

#time_signature(numer, denom, clocks, qnotes) ⇒ Object



120
121
# File 'lib/midilib/io/midifile.rb', line 120

def time_signature(numer, denom, clocks, qnotes)
end

#write16(val) ⇒ Object

Write a sixteen-bit value.



388
389
390
391
392
# File 'lib/midilib/io/midifile.rb', line 388

def write16(val)
	val = (-val) | 0x8000 if val < 0
	putc((val >> 8) & 0xff)
	putc(val & 0xff)
end

#write32(val) ⇒ Object

Write a 32-bit value.



395
396
397
398
399
400
401
# File 'lib/midilib/io/midifile.rb', line 395

def write32(val)
	val = (-val) | 0x80000000 if val < 0
	putc((val >> 24) & 0xff)
	putc((val >> 16) & 0xff)
	putc((val >> 8) & 0xff)
	putc(val & 0xff)
end

#write_var_len(val) ⇒ Object

Write a variable length value.



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/midilib/io/midifile.rb', line 404

def write_var_len(val)
	if val.zero?
 putc(0)
 return
	end

	buf = Array.new()

	buf << (val & 0x7f)
	while (value >>= 7) > 0
 buf << (val & 0x7f) | 0x80
	end

	buf.reverse.each { | b | putc(b) }
end