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

MThd_BYTE_ARRAY =

“MThd”

[77, 84, 104, 100]
MTrk_BYTE_ARRAY =

“MTrk”

[77, 84, 114, 107]
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.



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

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



39
40
41
# File 'lib/midilib/io/midifile.rb', line 39

def bytes_to_be_read
  @bytes_to_be_read
end

#curr_ticksObject

Current time, from delta-time in MIDI file



37
38
39
# File 'lib/midilib/io/midifile.rb', line 37

def curr_ticks
  @curr_ticks
end

#no_mergeObject

true means continued sysex are not collapsed



41
42
43
# File 'lib/midilib/io/midifile.rb', line 41

def no_merge
  @no_merge
end

#raw_dataObject

Returns the value of attribute raw_data.



47
48
49
# File 'lib/midilib/io/midifile.rb', line 47

def raw_data
  @raw_data
end

#raw_time_stamp_dataObject

Raw data info



45
46
47
# File 'lib/midilib/io/midifile.rb', line 45

def raw_time_stamp_data
  @raw_time_stamp_data
end

#raw_var_num_dataObject

Returns the value of attribute raw_var_num_data.



46
47
48
# File 'lib/midilib/io/midifile.rb', line 46

def raw_var_num_data
  @raw_var_num_data
end

#skip_initObject

true if initial garbage should be skipped



42
43
44
# File 'lib/midilib/io/midifile.rb', line 42

def skip_init
  @skip_init
end

#ticks_so_farObject

Number of delta-time ticks so far



38
39
40
# File 'lib/midilib/io/midifile.rb', line 38

def ticks_so_far
  @ticks_so_far
end

Instance Method Details

#arbitrary(msg) ⇒ Object



152
153
# File 'lib/midilib/io/midifile.rb', line 152

def arbitrary(msg)
end

#bad_byte(c) ⇒ Object

Handle an unexpected byte.



293
294
295
# File 'lib/midilib/io/midifile.rb', line 293

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



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/midilib/io/midifile.rb', line 334

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



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

def chan_pressure(chan, press)
end

#controller(chan, control, value) ⇒ Object



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

def controller(chan, control, value)
end

#end_trackObject



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

def end_track()
end

#eotObject



137
138
# File 'lib/midilib/io/midifile.rb', line 137

def eot()
end

#error(str) ⇒ Object

The default error handler.



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

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

#get_bytes(n) ⇒ Object

Return the next n bytes from @io as an array.



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

def get_bytes(n)
	buf = []
	n.times { buf << getc() }
	buf
end

#getcObject

This default getc implementation tries to read a single byte from io and returns it as an integer.



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

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

#handle_arbitrary(msg) ⇒ Object

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



369
370
371
372
# File 'lib/midilib/io/midifile.rb', line 369

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

#handle_sysex(msg) ⇒ Object

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



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

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

#header(format, ntrks, division) ⇒ Object

MIDI header.



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

def header(format, ntrks, division)
end

#key_signature(sharpflat, is_minor) ⇒ Object



149
150
# File 'lib/midilib/io/midifile.rb', line 149

def key_signature(sharpflat, is_minor)
end

#meta_event(type) ⇒ Object

Handle a meta event.



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/midilib/io/midifile.rb', line 298

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
	@raw_data.flatten!

	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



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

def meta_misc(type, msg)
end

#msgObject

Return a copy of the internal message buffer.



459
460
461
# File 'lib/midilib/io/midifile.rb', line 459

def msg
	return @msg_buf.dup()
end

#msg_add(c) ⇒ Object

Add a byte to the current message buffer.



441
442
443
# File 'lib/midilib/io/midifile.rb', line 441

def msg_add(c)
	@msg_buf << c
end

#msg_initObject

Initialize the internal message buffer.



454
455
456
# File 'lib/midilib/io/midifile.rb', line 454

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



447
448
449
450
451
# File 'lib/midilib/io/midifile.rb', line 447

def msg_read(n_bytes)
	@msg_buf += get_bytes(n_bytes)
	@msg_buf.flatten!
	return @msg_buf[-1]
end

#note_off(chan, note, vel) ⇒ Object



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

def note_off(chan, note, vel)
end

#note_on(chan, note, vel) ⇒ Object



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

def note_on(chan, note, vel)
end

#pitch_bend(chan, msb, lsb) ⇒ Object



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

def pitch_bend(chan, msb, lsb)
end

#pressure(chan, note, press) ⇒ Object



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

def pressure(chan, note, press)
end

#program(chan, program) ⇒ Object



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

def program(chan, program)
end

#read16Object

Read and return a sixteen bit value.



375
376
377
378
379
# File 'lib/midilib/io/midifile.rb', line 375

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

#read32Object

Read and return a 32-bit value.



382
383
384
385
386
387
# File 'lib/midilib/io/midifile.rb', line 382

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.



59
60
61
62
63
64
65
66
67
# File 'lib/midilib/io/midifile.rb', line 59

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.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/midilib/io/midifile.rb', line 189

def read_header
	@bytes_to_be_read = 0
	read_mt_header_string(MThd_BYTE_ARRAY, @skip_init) # "MThd"

	@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
    get_bytes(@bytes_to_be_read)
    @bytes_to_be_read = 0
	end

	return ntrks
end

#read_mt_header_string(bytes, 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.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/midilib/io/midifile.rb', line 160

def read_mt_header_string(bytes, skip)
	b = []
	bytes_to_read = 4
	while true
    data = get_bytes(bytes_to_read)
    b += data
    if b.length < 4
      error("unexpected EOF while trying to read header string #{s}")
    end

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

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

#read_trackObject

Read a track chunk.



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
281
282
283
284
285
286
287
288
289
290
# File 'lib/midilib/io/midifile.rb', line 210

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



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/midilib/io/midifile.rb', line 390

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



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

def sequence_number(num)
end

#sequencer_specific(type, msg) ⇒ Object



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

def sequencer_specific(type, msg)
end

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



143
144
# File 'lib/midilib/io/midifile.rb', line 143

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

#start_track(bytes_to_be_read) ⇒ Object



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

def start_track(bytes_to_be_read)
end

#sysex(msg) ⇒ Object



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

def sysex(msg)
end

#tempo(microsecs) ⇒ Object



146
147
# File 'lib/midilib/io/midifile.rb', line 146

def tempo(microsecs)
end

#text(type, msg) ⇒ Object



134
135
# File 'lib/midilib/io/midifile.rb', line 134

def text(type, msg)
end

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



140
141
# File 'lib/midilib/io/midifile.rb', line 140

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

#write16(val) ⇒ Object

Write a sixteen-bit value.



408
409
410
411
412
# File 'lib/midilib/io/midifile.rb', line 408

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.



415
416
417
418
419
420
421
# File 'lib/midilib/io/midifile.rb', line 415

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.



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/midilib/io/midifile.rb', line 424

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

	buf = []

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

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