class MIDI::IO::SeqWriter

Public Class Methods

new(seq, midi_format = 1) { |num_tracks, index| ... } click to toggle source
# File lib/midilib/io/seqwriter.rb, line 9
def initialize(seq, midi_format = 1, &block) # :yields: num_tracks, index
  @seq = seq
  @midi_format = midi_format || 1
  @update_block = block
end

Public Instance Methods

possibly_munge_due_to_running_status_byte(data, prev_status) click to toggle source

If we can use a running status byte, delete the status byte from the given data. Return the status to remember for next time as the running status byte for this event.

# File lib/midilib/io/seqwriter.rb, line 86
def possibly_munge_due_to_running_status_byte(data, prev_status)
  status = data[0]
  return status if status >= 0xf0 || prev_status >= 0xf0

  chan = (status & 0x0f)
  return status if chan != (prev_status & 0x0f)

  status = (status & 0xf0)
  prev_status = (prev_status & 0xf0)

  # Both events are on the same channel. If the two status bytes are
  # exactly the same, the rest is trivial. If it's note on/note off,
  # we can combine those further.
  if status == prev_status
    data[0, 1] = [] # delete status byte from data
    status + chan
  elsif status == NOTE_OFF && data[2] == 64
    # If we see a note off and the velocity is 64, we can store
    # a note on with a velocity of 0. If the velocity isn't 64
    # then storing a note on would be bad because the would be
    # changed to 64 when reading the file back in.
    data[2] = 0 # set vel to 0; do before possible shrinking
    status = NOTE_ON + chan
    if prev_status == NOTE_ON
      data[0, 1] = [] # delete status byte
    else
      data[0] = status
    end
    status
  else
    # Can't compress data
    status + chan
  end
end
write16(val) click to toggle source
# File lib/midilib/io/seqwriter.rb, line 135
def write16(val)
  val = (-val | 0x8000) if val < 0

  @io.putc((val >> 8) & 0xff)
  @io.putc(val & 0xff)
  @bytes_written += 2
end
write32(val) click to toggle source
# File lib/midilib/io/seqwriter.rb, line 143
def write32(val)
  val = (-val | 0x80000000) if val < 0

  @io.putc((val >> 24) & 0xff)
  @io.putc((val >> 16) & 0xff)
  @io.putc((val >> 8) & 0xff)
  @io.putc(val & 0xff)
  @bytes_written += 4
end
write_bytes(bytes) click to toggle source
# File lib/midilib/io/seqwriter.rb, line 153
def write_bytes(bytes)
  bytes.each { |b| @io.putc(b) }
  bytes.length
end
write_header() click to toggle source
# File lib/midilib/io/seqwriter.rb, line 38
def write_header
  @io.print 'MThd'
  write32(6)
  write16(@midi_format) # Ignore sequence format; write as format 1 or 0, default 1
  write16(@seq.tracks.length)
  write16(@seq.ppqn)
end
write_instrument(instrument) click to toggle source
# File lib/midilib/io/seqwriter.rb, line 121
def write_instrument(instrument)
  return if instrument.nil?

  event = MetaEvent.new(META_INSTRUMENT, instrument)
  write_var_len(0)
  data = event.data_as_bytes
  @bytes_written += write_bytes(data)
end
write_to(io) click to toggle source

Writes a MIDI format 1 file.

# File lib/midilib/io/seqwriter.rb, line 16
def write_to(io)
  if @midi_format == 0
    # merge tracks before writing
    merged_seq = Sequence.new
    merged_track = Track.new(merged_seq)
    merged_seq.tracks << merged_track
    @seq.each do |track|
      merged_track.merge(track.events)
    end
    @seq = merged_seq # replace
  end

  @io = io
  @bytes_written = 0
  write_header
  @update_block.call(nil, @seq.tracks.length, 0) if @update_block
  @seq.tracks.each_with_index do |track, i|
    write_track(track)
    @update_block.call(track, @seq.tracks.length, i) if @update_block
  end
end
write_track(track) click to toggle source
# File lib/midilib/io/seqwriter.rb, line 46
def write_track(track)
  @io.print 'MTrk'
  track_size_file_pos = @io.tell
  write32(0)                # Dummy byte count; overwritten later
  @bytes_written = 0        # Reset after previous write

  write_instrument(track.instrument)

  prev_status = 0
  track.events.each do |event|
    write_var_len(event.delta_time) unless event.is_a?(Realtime)

    data = event.data_as_bytes
    status = data[0] # status byte plus channel number, if any

    # running status byte
    status = possibly_munge_due_to_running_status_byte(data, prev_status)

    @bytes_written += write_bytes(data)

    prev_status = status
  end

  # Write track end event if the track doesn't have one.
  unless track.events.last.is_a?(MetaEvent) && track.events.last.meta_type == META_TRACK_END
    event = MetaEvent.new(META_TRACK_END)
    write_var_len(0)
    @bytes_written += write_bytes(event.data_as_bytes)
  end

  # Go back to beginning of track data and write number of bytes,
  # then come back here to end of file.
  @io.seek(track_size_file_pos)
  write32(@bytes_written)
  @io.seek(0, ::IO::SEEK_END)
end
write_var_len(val) click to toggle source
# File lib/midilib/io/seqwriter.rb, line 130
def write_var_len(val)
  buffer = Utils.as_var_len(val)
  @bytes_written += write_bytes(buffer)
end