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.



29
30
31
32
33
34
35
# File 'lib/midilib/io/midifile.rb', line 29

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

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



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

def bytes_to_be_read
  @bytes_to_be_read
end

#curr_ticksObject

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



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

def curr_ticks
  @curr_ticks
end

#no_mergeObject

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



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

def no_merge
  @no_merge
end

#raw_dataObject

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



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

def raw_data
  @raw_data
end

#raw_time_stamp_dataObject

Raw data info



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

def raw_time_stamp_data
  @raw_time_stamp_data
end

#raw_var_num_dataObject

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



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

def raw_var_num_data
  @raw_var_num_data
end

#skip_initObject

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



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

def skip_init
  @skip_init
end

#ticks_so_farObject

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



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

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.



268
269
270
# File 'lib/midilib/io/midifile.rb', line 268

def bad_byte(c)
  error(format('unexpected byte: 0x%02x', c))
end

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

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



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/midilib/io/midifile.rb', line 309

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#{format('%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

#get_bytes(n) ⇒ Object

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



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

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.



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

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

#handle_arbitrary(msg) ⇒ Object

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



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

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

#handle_sysex(msg) ⇒ Object

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



338
339
340
341
# File 'lib/midilib/io/midifile.rb', line 338

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.



273
274
275
276
277
278
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
# File 'lib/midilib/io/midifile.rb', line 273

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



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

def msg
  @msg_buf.dup
end

#msg_add(c) ⇒ Object

Add a byte to the current message buffer.



416
417
418
# File 'lib/midilib/io/midifile.rb', line 416

def msg_add(c)
  @msg_buf << c
end

#msg_initObject

Initialize the internal message buffer.



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

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



422
423
424
425
426
# File 'lib/midilib/io/midifile.rb', line 422

def msg_read(n_bytes)
  @msg_buf += get_bytes(n_bytes)
  @msg_buf.flatten!
  @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.



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

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

#read32Object

Read and return a 32-bit value.



357
358
359
360
361
362
# File 'lib/midilib/io/midifile.rb', line 357

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

#read_from(io) ⇒ Object

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



39
40
41
42
43
44
45
46
47
# File 'lib/midilib/io/midifile.rb', line 39

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.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/midilib/io/midifile.rb', line 166

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

  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.



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

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

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



187
188
189
190
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
# File 'lib/midilib/io/midifile.rb', line 187

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

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

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



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/midilib/io/midifile.rb', line 365

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



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

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.



390
391
392
393
394
395
396
# File 'lib/midilib/io/midifile.rb', line 390

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.



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/midilib/io/midifile.rb', line 399

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