class MIDI::IO::MIDIFile

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.

Constants

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

Attributes

bytes_to_be_read[RW]
curr_ticks[RW]
no_merge[RW]
raw_data[RW]
raw_time_stamp_data[RW]

Raw data info

raw_var_num_data[RW]
skip_init[RW]
ticks_so_far[RW]

Public Class Methods

new() click to toggle source
# 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

Public Instance Methods

arbitrary(msg) click to toggle source
# File lib/midilib/io/midifile.rb, line 132
def arbitrary(msg)
end
bad_byte(c) click to toggle source

Handle an unexpected byte.

# 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) click to toggle source

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

# 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) click to toggle source
# File lib/midilib/io/midifile.rb, line 99
def chan_pressure(chan, press)
end
controller(chan, control, value) click to toggle source
# File lib/midilib/io/midifile.rb, line 90
def controller(chan, control, value)
end
end_track() click to toggle source
# File lib/midilib/io/midifile.rb, line 78
def end_track
end
eot() click to toggle source
# File lib/midilib/io/midifile.rb, line 117
def eot
end
error(str) click to toggle source

The default error handler.

# 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) click to toggle source

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

# File lib/midilib/io/midifile.rb, line 57
def get_bytes(n)
  buf = []
  n.times { buf << getc }
  buf
end
getc() click to toggle source

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

# File lib/midilib/io/midifile.rb, line 51
def getc
  @bytes_to_be_read -= 1
  @io.readbyte
end
handle_arbitrary(msg) click to toggle source

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

# File lib/midilib/io/midifile.rb, line 344
def handle_arbitrary(msg)
  @raw_data = msg.dup
  arbitrary(msg)
end
handle_sysex(msg) click to toggle source

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

# File lib/midilib/io/midifile.rb, line 338
def handle_sysex(msg)
  @raw_data = msg.dup
  sysex(msg)
end
header(format, ntrks, division) click to toggle source

MIDI header.

# File lib/midilib/io/midifile.rb, line 72
def header(format, ntrks, division)
end
key_signature(sharpflat, is_minor) click to toggle source
# File lib/midilib/io/midifile.rb, line 129
def key_signature(sharpflat, is_minor)
end
meta_event(type) click to toggle source

Handle a meta event.

# 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) click to toggle source
# File lib/midilib/io/midifile.rb, line 105
def meta_misc(type, msg)
end
msg() click to toggle source

Return a copy of the internal message buffer.

# File lib/midilib/io/midifile.rb, line 434
def msg
  @msg_buf.dup
end
msg_add(c) click to toggle source

Add a byte to the current message buffer.

# File lib/midilib/io/midifile.rb, line 416
def msg_add(c)
  @msg_buf << c
end
msg_init() click to toggle source

Initialize the internal message buffer.

# File lib/midilib/io/midifile.rb, line 429
def msg_init
  @msg_buf = []
end
msg_read(n_bytes) click to toggle source

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

# 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) click to toggle source
# File lib/midilib/io/midifile.rb, line 84
def note_off(chan, note, vel)
end
note_on(chan, note, vel) click to toggle source
# File lib/midilib/io/midifile.rb, line 81
def note_on(chan, note, vel)
end
pitch_bend(chan, msb, lsb) click to toggle source
# File lib/midilib/io/midifile.rb, line 93
def pitch_bend(chan, msb, lsb)
end
pressure(chan, note, press) click to toggle source
# File lib/midilib/io/midifile.rb, line 87
def pressure(chan, note, press)
end
program(chan, program) click to toggle source
# File lib/midilib/io/midifile.rb, line 96
def program(chan, program)
end
read16() click to toggle source

Read and return a sixteen bit value.

# File lib/midilib/io/midifile.rb, line 350
def read16
  val = (getc << 8) + getc
  val = -(val & 0x7fff) if (val & 0x8000).nonzero?
  val
end
read32() click to toggle source

Read and return a 32-bit value.

# 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) click to toggle source

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

# 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_header() click to toggle source

Read a header chunk.

# 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) click to toggle source

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

# 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_track() click to toggle source

Read a track chunk.

# 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_len() click to toggle source

Read a varlen value.

# 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) click to toggle source
# File lib/midilib/io/midifile.rb, line 111
def sequence_number(num)
end
sequencer_specific(type, msg) click to toggle source
# File lib/midilib/io/midifile.rb, line 108
def sequencer_specific(type, msg)
end
smpte(hour, min, sec, frame, fract) click to toggle source
# File lib/midilib/io/midifile.rb, line 123
def smpte(hour, min, sec, frame, fract)
end
start_track(bytes_to_be_read) click to toggle source
# File lib/midilib/io/midifile.rb, line 75
def start_track(bytes_to_be_read)
end
sysex(msg) click to toggle source
# File lib/midilib/io/midifile.rb, line 102
def sysex(msg)
end
tempo(microsecs) click to toggle source
# File lib/midilib/io/midifile.rb, line 126
def tempo(microsecs)
end
text(type, msg) click to toggle source
# File lib/midilib/io/midifile.rb, line 114
def text(type, msg)
end
time_signature(numer, denom, clocks, qnotes) click to toggle source
# File lib/midilib/io/midifile.rb, line 120
def time_signature(numer, denom, clocks, qnotes)
end
write16(val) click to toggle source

Write a sixteen-bit value.

# 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) click to toggle source

Write a 32-bit value.

# 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) click to toggle source

Write a variable length value.

# 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