Class: MIDI::IO::MIDIFile
- Inherits:
-
Object
- Object
- MIDI::IO::MIDIFile
- 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
Constant Summary collapse
- 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
-
#bytes_to_be_read ⇒ Object
Counts number of bytes expected.
-
#curr_ticks ⇒ Object
Current time, from delta-time in MIDI file.
-
#no_merge ⇒ Object
true means continued sysex are not collapsed.
-
#raw_data ⇒ Object
Returns the value of attribute raw_data.
-
#raw_time_stamp_data ⇒ Object
Raw data info.
-
#raw_var_num_data ⇒ Object
Returns the value of attribute raw_var_num_data.
-
#skip_init ⇒ Object
true if initial garbage should be skipped.
-
#ticks_so_far ⇒ Object
Number of delta-time ticks so far.
Instance Method Summary collapse
- #arbitrary(msg) ⇒ Object
-
#bad_byte(c) ⇒ Object
Handle an unexpected byte.
-
#chan_message(running, status, c1, c2) ⇒ Object
Handle a channel message (note on, note off, etc.).
- #chan_pressure(chan, press) ⇒ Object
- #controller(chan, control, value) ⇒ Object
- #end_track ⇒ Object
- #eot ⇒ Object
-
#error(str) ⇒ Object
The default error handler.
-
#getc ⇒ Object
This default getc implementation tries to read a single character from io.
-
#handle_arbitrary(msg) ⇒ Object
Copy message into raw data array, then call arbitrary().
-
#handle_sysex(msg) ⇒ Object
Copy message into raw data array, then call sysex().
-
#header(format, ntrks, division) ⇒ Object
MIDI header.
-
#initialize ⇒ MIDIFile
constructor
A new instance of MIDIFile.
- #key_signature(sharpflat, is_minor) ⇒ Object
-
#meta_event(type) ⇒ Object
Handle a meta event.
- #meta_misc(type, msg) ⇒ Object
-
#msg ⇒ Object
Return a copy of the internal message buffer.
-
#msg_add(c) ⇒ Object
Add a byte to the current message buffer.
-
#msg_init ⇒ Object
Initialize the internal message buffer.
-
#msg_read(n_bytes) ⇒ Object
Read and add a number of bytes to the message buffer.
- #note_off(chan, note, vel) ⇒ Object
- #note_on(chan, note, vel) ⇒ Object
- #pitch_bend(chan, msb, lsb) ⇒ Object
- #pressure(chan, note, press) ⇒ Object
- #program(chan, program) ⇒ Object
-
#read16 ⇒ Object
Read and return a sixteen bit value.
-
#read32 ⇒ Object
Read and return a 32-bit value.
-
#read_from(io) ⇒ Object
The only public method.
-
#read_header ⇒ Object
Read a header chunk.
-
#read_mt_header_string(s, skip) ⇒ Object
Read through ‘MThd’ or ‘MTrk’ header string.
-
#read_track ⇒ Object
Read a track chunk.
-
#read_var_len ⇒ Object
Read a varlen value.
- #sequence_number(num) ⇒ Object
- #sequencer_specific(type, msg) ⇒ Object
- #smpte(hour, min, sec, frame, fract) ⇒ Object
- #start_track(bytes_to_be_read) ⇒ Object
- #sysex(msg) ⇒ Object
- #tempo(microsecs) ⇒ Object
- #text(type, msg) ⇒ Object
- #time_signature(numer, denom, clocks, qnotes) ⇒ Object
-
#write16(val) ⇒ Object
Write a sixteen-bit value.
-
#write32(val) ⇒ Object
Write a 32-bit value.
-
#write_var_len(val) ⇒ Object
Write a variable length value.
Constructor Details
#initialize ⇒ MIDIFile
Returns a new instance of MIDIFile.
36 37 38 39 40 41 42 |
# File 'lib/midilib/io/midifile.rb', line 36 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_read ⇒ Object
Counts number of bytes expected
26 27 28 |
# File 'lib/midilib/io/midifile.rb', line 26 def bytes_to_be_read @bytes_to_be_read end |
#curr_ticks ⇒ Object
Current time, from delta-time in MIDI file
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def curr_ticks @curr_ticks end |
#no_merge ⇒ Object
true means continued sysex are not collapsed
28 29 30 |
# File 'lib/midilib/io/midifile.rb', line 28 def no_merge @no_merge end |
#raw_data ⇒ Object
Returns the value of attribute raw_data.
34 35 36 |
# File 'lib/midilib/io/midifile.rb', line 34 def raw_data @raw_data end |
#raw_time_stamp_data ⇒ Object
Raw data info
32 33 34 |
# File 'lib/midilib/io/midifile.rb', line 32 def raw_time_stamp_data @raw_time_stamp_data end |
#raw_var_num_data ⇒ Object
Returns the value of attribute raw_var_num_data.
33 34 35 |
# File 'lib/midilib/io/midifile.rb', line 33 def raw_var_num_data @raw_var_num_data end |
#skip_init ⇒ Object
true if initial garbage should be skipped
29 30 31 |
# File 'lib/midilib/io/midifile.rb', line 29 def skip_init @skip_init end |
#ticks_so_far ⇒ Object
Number of delta-time ticks so far
25 26 27 |
# File 'lib/midilib/io/midifile.rb', line 25 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.
274 275 276 |
# File 'lib/midilib/io/midifile.rb', line 274 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.)
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/midilib/io/midifile.rb', line 314 def (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
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_track ⇒ Object
78 79 |
# File 'lib/midilib/io/midifile.rb', line 78 def end_track() end |
#eot ⇒ Object
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 |
#getc ⇒ Object
This default getc implementation tries to read a single character from io.
58 59 60 61 |
# File 'lib/midilib/io/midifile.rb', line 58 def getc @bytes_to_be_read -= 1 return @io.getc() end |
#handle_arbitrary(msg) ⇒ Object
Copy message into raw data array, then call arbitrary().
349 350 351 352 |
# File 'lib/midilib/io/midifile.rb', line 349 def handle_arbitrary(msg) @raw_data = msg.dup() arbitrary(msg) end |
#handle_sysex(msg) ⇒ Object
Copy message into raw data array, then call sysex().
343 344 345 346 |
# File 'lib/midilib/io/midifile.rb', line 343 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.
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 307 308 309 310 311 |
# File 'lib/midilib/io/midifile.rb', line 279 def (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 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 (type, m) end end |
#meta_misc(type, msg) ⇒ Object
105 106 |
# File 'lib/midilib/io/midifile.rb', line 105 def (type, msg) end |
#msg ⇒ Object
Return a copy of the internal message buffer.
439 440 441 |
# File 'lib/midilib/io/midifile.rb', line 439 def msg return @msg_buf.dup() end |
#msg_add(c) ⇒ Object
Add a byte to the current message buffer.
421 422 423 |
# File 'lib/midilib/io/midifile.rb', line 421 def msg_add(c) @msg_buf << c end |
#msg_init ⇒ Object
Initialize the internal message buffer.
434 435 436 |
# File 'lib/midilib/io/midifile.rb', line 434 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).
427 428 429 430 431 |
# File 'lib/midilib/io/midifile.rb', line 427 def msg_read(n_bytes) @msg_buf << @io.read(n_bytes) @bytes_to_be_read -= n_bytes return @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 |
#read16 ⇒ Object
Read and return a sixteen bit value.
355 356 357 358 359 |
# File 'lib/midilib/io/midifile.rb', line 355 def read16 val = (getc() << 8) + getc() val = -(val & 0x7fff) if (val & 0x8000).nonzero? return val end |
#read32 ⇒ Object
Read and return a 32-bit value.
362 363 364 365 366 367 |
# File 'lib/midilib/io/midifile.rb', line 362 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.
46 47 48 49 50 51 52 53 54 |
# File 'lib/midilib/io/midifile.rb', line 46 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 ⇒ Object
Read a header chunk.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/midilib/io/midifile.rb', line 170 def read_header @bytes_to_be_read = 0 read_mt_header_string('MThd', @skip_init) @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 @io.read(@bytes_to_be_read) @bytes_to_be_read = 0 end return ntrks end |
#read_mt_header_string(s, 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.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/midilib/io/midifile.rb', line 140 def read_mt_header_string(s, skip) b = '' bytes_to_read = 4 while true b << @io.read(bytes_to_read) if b.length < 4 error("unexpected EOF while trying to read header" + " string #{s}") end @bytes_to_be_read -= bytes_to_read # See if we found the string we're looking for return if b == s if skip # Try again with the next char i = b.index(s[0], 1) if i.nil? b = '' bytes_to_read = 4 else b = b[i..-1] bytes_to_read = 4 - i end else error("header string #{s} not found") end end end |
#read_track ⇒ Object
Read a track chunk.
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 266 267 268 269 270 271 |
# File 'lib/midilib/io/midifile.rb', line 191 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', 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. (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()) (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_len ⇒ Object
Read a varlen value.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/midilib/io/midifile.rb', line 370 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
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.
388 389 390 391 392 |
# File 'lib/midilib/io/midifile.rb', line 388 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.
395 396 397 398 399 400 401 |
# File 'lib/midilib/io/midifile.rb', line 395 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.
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/midilib/io/midifile.rb', line 404 def write_var_len(val) if val.zero? putc(0) return end buf = Array.new() buf << (val & 0x7f) while (value >>= 7) > 0 buf << (val & 0x7f) | 0x80 end buf.reverse.each { | b | putc(b) } end |