Class: MIDI::IO::SeqReader

Inherits:
MIDIFile show all
Defined in:
lib/midilib/io/seqreader.rb

Overview

Reads MIDI files. As a subclass of MIDIFile, this class implements the callback methods for each MIDI event and use them to build Track and Event objects and give the tracks to a Sequence.

We append new events to the end of a track’s event list, bypassing a call to Track.#add. This means that we must call Track.recalc_times at the end of the track so it can update each event with its time from the track’s start (see end_track below).

META_TRACK_END events are not added to tracks. This way, we don’t have to worry about making sure the last event is always a track end event. We rely on the SeqWriter to append a META_TRACK_END event to each track when it is output.

Constant Summary

Constants inherited from MIDIFile

MIDIFile::MThd_BYTE_ARRAY, MIDIFile::MTrk_BYTE_ARRAY, MIDIFile::NUM_DATA_BYTES

Instance Attribute Summary

Attributes inherited from MIDIFile

#bytes_to_be_read, #curr_ticks, #no_merge, #raw_data, #raw_time_stamp_data, #raw_var_num_data, #skip_init, #ticks_so_far

Instance Method Summary collapse

Methods inherited from MIDIFile

#arbitrary, #bad_byte, #chan_message, #eot, #error, #get_bytes, #getc, #handle_arbitrary, #handle_sysex, #meta_event, #msg, #msg_add, #msg_init, #msg_read, #read16, #read32, #read_from, #read_header, #read_mt_header_string, #read_track, #read_var_len, #sequence_number, #sequencer_specific, #smpte, #write16, #write32, #write_var_len

Constructor Details

#initialize(seq, &block) ⇒ SeqReader

The optional &block is called once at the start of the file and again at the end of each track. There are three arguments to the block: the track, the track number (1 through n), and the total number of tracks.



26
27
28
29
30
31
32
# File 'lib/midilib/io/seqreader.rb', line 26

def initialize(seq, &block) # :yields: track, num_tracks, index
  super()
  @seq = seq
  @track = nil
  @chan_mask = 0
  @update_block = block
end

Instance Method Details

#chan_pressure(chan, press) ⇒ Object



137
138
139
140
# File 'lib/midilib/io/seqreader.rb', line 137

def chan_pressure(chan, press)
  @track.events << ChannelPressure.new(chan, press, @curr_ticks)
  track_uses_channel(chan)
end

#controller(chan, control, value) ⇒ Object



122
123
124
125
# File 'lib/midilib/io/seqreader.rb', line 122

def controller(chan, control, value)
  @track.events << Controller.new(chan, control, value, @curr_ticks)
  track_uses_channel(chan)
end

#end_trackObject



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/midilib/io/seqreader.rb', line 49

def end_track
  # Turn off any pending note on messages
  @pending.each { |on| make_note_off(on, 64) }
  @pending = nil

  # Don't bother adding the META_TRACK_END event to the track.
  # This way, we don't have to worry about making sure the
  # last event is always a track end event.

  # Let the track calculate event times from start of track. This is
  # in lieu of calling Track.add for each event.
  @track.recalc_times

  # Store bitmask of all channels used into track
  @track.channels_used = @chan_mask

  # call update block
  @update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
end

#header(format, ntrks, division) ⇒ Object



34
35
36
37
38
39
40
# File 'lib/midilib/io/seqreader.rb', line 34

def header(format, ntrks, division)
  @seq.format = format
  @seq.ppqn = division

  @ntrks = ntrks
  @update_block.call(nil, @ntrks, 0) if @update_block
end

#key_signature(sharpflat, is_minor) ⇒ Object



195
196
197
# File 'lib/midilib/io/seqreader.rb', line 195

def key_signature(sharpflat, is_minor)
  @track.events << KeySig.new(sharpflat, is_minor, @curr_ticks)
end

#make_note_off(on, vel) ⇒ Object



110
111
112
113
114
115
# File 'lib/midilib/io/seqreader.rb', line 110

def make_note_off(on, vel)
  off = NoteOff.new(on.channel, on.note, vel, @curr_ticks)
  @track.events << off
  on.off = off
  off.on = on
end

#meta_misc(type, msg) ⇒ Object



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

def meta_misc(type, msg)
  @track.events << MetaEvent.new(type, msg, @curr_ticks)
end

#note_off(chan, note, vel) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/midilib/io/seqreader.rb', line 81

def note_off(chan, note, vel)
  # Find note on, create note off, connect the two, and remove
  # note on from pending list.

  corresp_note_on = nil

  @pending.each_with_index do |on, i|
    next unless on.note == note && on.channel == chan

    @pending.delete_at(i)
    corresp_note_on = on
    break
  end

  if corresp_note_on
    make_note_off(corresp_note_on, vel)
  else
    # When a corresponding note on is missing,
    # keep note off as input with lefting on/off attr to nil.
    off = NoteOff.new(chan, note, vel, @curr_ticks)
    @track.events << off

    if $DEBUG
      warn "note off with no earlier note on (ch #{chan}, note" +
        " #{note}, vel #{vel})"
    end
  end
end

#note_on(chan, note, vel) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/midilib/io/seqreader.rb', line 69

def note_on(chan, note, vel)
  if vel == 0
    note_off(chan, note, 64)
    return
  end

  on = NoteOn.new(chan, note, vel, @curr_ticks)
  @track.events << on
  @pending << on
  track_uses_channel(chan)
end

#pitch_bend(chan, lsb, msb) ⇒ Object



127
128
129
130
# File 'lib/midilib/io/seqreader.rb', line 127

def pitch_bend(chan, lsb, msb)
  @track.events << PitchBend.new(chan, (msb << 7) + lsb, @curr_ticks)
  track_uses_channel(chan)
end

#pressure(chan, note, press) ⇒ Object



117
118
119
120
# File 'lib/midilib/io/seqreader.rb', line 117

def pressure(chan, note, press)
  @track.events << PolyPressure.new(chan, note, press, @curr_ticks)
  track_uses_channel(chan)
end

#program(chan, program) ⇒ Object



132
133
134
135
# File 'lib/midilib/io/seqreader.rb', line 132

def program(chan, program)
  @track.events << ProgramChange.new(chan, program, @curr_ticks)
  track_uses_channel(chan)
end

#start_trackObject



42
43
44
45
46
47
# File 'lib/midilib/io/seqreader.rb', line 42

def start_track
  @track = Track.new(@seq)
  @seq.tracks << @track

  @pending = []
end

#sysex(msg) ⇒ Object



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

def sysex(msg)
  @track.events << SystemExclusive.new(msg, @curr_ticks)
end

#tempo(microsecs) ⇒ Object

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

++



191
192
193
# File 'lib/midilib/io/seqreader.rb', line 191

def tempo(microsecs)
  @track.events << Tempo.new(microsecs, @curr_ticks)
end

#text(type, msg) ⇒ Object

def sequence_number(num)

end

++



158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/midilib/io/seqreader.rb', line 158

def text(type, msg)
  case type
  when META_TEXT, META_LYRIC, META_CUE, META_SEQ_NAME, META_COPYRIGHT
    @track.events << MetaEvent.new(type, msg, @curr_ticks)
  when META_INSTRUMENT
    @track.instrument = msg
  when META_MARKER
    @track.events << Marker.new(msg, @curr_ticks)
  else
    warn "text = #{msg}, type = #{type}" if $DEBUG
  end
end

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

– Don’t bother adding the META_TRACK_END event to the track. This way, we don’t have to worry about always making sure the last event is always a track end event. We just have to make sure to write one when the track is output back to a file.

def eot()
    @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
end

++



181
182
183
184
# File 'lib/midilib/io/seqreader.rb', line 181

def time_signature(numer, denom, clocks, qnotes)
  @seq.time_signature(numer, denom, clocks, qnotes)
  @track.events << TimeSig.new(numer, denom, clocks, qnotes, @curr_ticks)
end

#track_uses_channel(chan) ⇒ Object

Return true if the current track uses the specified channel.



205
206
207
# File 'lib/midilib/io/seqreader.rb', line 205

def track_uses_channel(chan)
  @chan_mask |= (1 << chan)
end