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
- 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
-
#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.
-
#get_bytes(n) ⇒ Object
Return the next
n
bytes from @io as an array. -
#getc ⇒ Object
This default getc implementation tries to read a single byte from io and returns it as an integer.
-
#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(bytes, 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.
49 50 51 52 53 54 55 |
# File 'lib/midilib/io/midifile.rb', line 49 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
39 40 41 |
# File 'lib/midilib/io/midifile.rb', line 39 def bytes_to_be_read @bytes_to_be_read end |
#curr_ticks ⇒ Object
Current time, from delta-time in MIDI file
37 38 39 |
# File 'lib/midilib/io/midifile.rb', line 37 def curr_ticks @curr_ticks end |
#no_merge ⇒ Object
true means continued sysex are not collapsed
41 42 43 |
# File 'lib/midilib/io/midifile.rb', line 41 def no_merge @no_merge end |
#raw_data ⇒ Object
Returns the value of attribute raw_data.
47 48 49 |
# File 'lib/midilib/io/midifile.rb', line 47 def raw_data @raw_data end |
#raw_time_stamp_data ⇒ Object
Raw data info
45 46 47 |
# File 'lib/midilib/io/midifile.rb', line 45 def raw_time_stamp_data @raw_time_stamp_data end |
#raw_var_num_data ⇒ Object
Returns the value of attribute raw_var_num_data.
46 47 48 |
# File 'lib/midilib/io/midifile.rb', line 46 def raw_var_num_data @raw_var_num_data end |
#skip_init ⇒ Object
true if initial garbage should be skipped
42 43 44 |
# File 'lib/midilib/io/midifile.rb', line 42 def skip_init @skip_init end |
#ticks_so_far ⇒ Object
Number of delta-time ticks so far
38 39 40 |
# File 'lib/midilib/io/midifile.rb', line 38 def ticks_so_far @ticks_so_far end |
Instance Method Details
#arbitrary(msg) ⇒ Object
152 153 |
# File 'lib/midilib/io/midifile.rb', line 152 def arbitrary(msg) end |
#bad_byte(c) ⇒ Object
Handle an unexpected byte.
293 294 295 |
# File 'lib/midilib/io/midifile.rb', line 293 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.)
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/midilib/io/midifile.rb', line 334 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
119 120 |
# File 'lib/midilib/io/midifile.rb', line 119 def chan_pressure(chan, press) end |
#controller(chan, control, value) ⇒ Object
110 111 |
# File 'lib/midilib/io/midifile.rb', line 110 def controller(chan, control, value) end |
#end_track ⇒ Object
98 99 |
# File 'lib/midilib/io/midifile.rb', line 98 def end_track() end |
#eot ⇒ Object
137 138 |
# File 'lib/midilib/io/midifile.rb', line 137 def eot() end |
#error(str) ⇒ Object
The default error handler.
84 85 86 87 |
# File 'lib/midilib/io/midifile.rb', line 84 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.
77 78 79 80 81 |
# File 'lib/midilib/io/midifile.rb', line 77 def get_bytes(n) buf = [] n.times { buf << getc() } buf end |
#getc ⇒ Object
This default getc implementation tries to read a single byte from io and returns it as an integer.
71 72 73 74 |
# File 'lib/midilib/io/midifile.rb', line 71 def getc @bytes_to_be_read -= 1 @io.readbyte() end |
#handle_arbitrary(msg) ⇒ Object
Copy message into raw data array, then call arbitrary().
369 370 371 372 |
# File 'lib/midilib/io/midifile.rb', line 369 def handle_arbitrary(msg) @raw_data = msg.dup() arbitrary(msg) end |
#handle_sysex(msg) ⇒ Object
Copy message into raw data array, then call sysex().
363 364 365 366 |
# File 'lib/midilib/io/midifile.rb', line 363 def handle_sysex(msg) @raw_data = msg.dup() sysex(msg) end |
#header(format, ntrks, division) ⇒ Object
MIDI header.
92 93 |
# File 'lib/midilib/io/midifile.rb', line 92 def header(format, ntrks, division) end |
#key_signature(sharpflat, is_minor) ⇒ Object
149 150 |
# File 'lib/midilib/io/midifile.rb', line 149 def key_signature(sharpflat, is_minor) end |
#meta_event(type) ⇒ Object
Handle a meta event.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/midilib/io/midifile.rb', line 298 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 @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 ? false : true) when META_SEQ_SPECIF sequencer_specific(type, m) else (type, m) end end |
#meta_misc(type, msg) ⇒ Object
125 126 |
# File 'lib/midilib/io/midifile.rb', line 125 def (type, msg) end |
#msg ⇒ Object
Return a copy of the internal message buffer.
459 460 461 |
# File 'lib/midilib/io/midifile.rb', line 459 def msg return @msg_buf.dup() end |
#msg_add(c) ⇒ Object
Add a byte to the current message buffer.
441 442 443 |
# File 'lib/midilib/io/midifile.rb', line 441 def msg_add(c) @msg_buf << c end |
#msg_init ⇒ Object
Initialize the internal message buffer.
454 455 456 |
# File 'lib/midilib/io/midifile.rb', line 454 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).
447 448 449 450 451 |
# File 'lib/midilib/io/midifile.rb', line 447 def msg_read(n_bytes) @msg_buf += get_bytes(n_bytes) @msg_buf.flatten! return @msg_buf[-1] end |
#note_off(chan, note, vel) ⇒ Object
104 105 |
# File 'lib/midilib/io/midifile.rb', line 104 def note_off(chan, note, vel) end |
#note_on(chan, note, vel) ⇒ Object
101 102 |
# File 'lib/midilib/io/midifile.rb', line 101 def note_on(chan, note, vel) end |
#pitch_bend(chan, msb, lsb) ⇒ Object
113 114 |
# File 'lib/midilib/io/midifile.rb', line 113 def pitch_bend(chan, msb, lsb) end |
#pressure(chan, note, press) ⇒ Object
107 108 |
# File 'lib/midilib/io/midifile.rb', line 107 def pressure(chan, note, press) end |
#program(chan, program) ⇒ Object
116 117 |
# File 'lib/midilib/io/midifile.rb', line 116 def program(chan, program) end |
#read16 ⇒ Object
Read and return a sixteen bit value.
375 376 377 378 379 |
# File 'lib/midilib/io/midifile.rb', line 375 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.
382 383 384 385 386 387 |
# File 'lib/midilib/io/midifile.rb', line 382 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.
59 60 61 62 63 64 65 66 67 |
# File 'lib/midilib/io/midifile.rb', line 59 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.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/midilib/io/midifile.rb', line 189 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 return 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.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/midilib/io/midifile.rb', line 160 def read_mt_header_string(bytes, skip) b = [] bytes_to_read = 4 while true data = get_bytes(bytes_to_read) b += data if b.length < 4 error("unexpected EOF while trying to read header string #{s}") end # 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 ⇒ Object
Read a track chunk.
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 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/midilib/io/midifile.rb', line 210 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 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.
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'lib/midilib/io/midifile.rb', line 390 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
131 132 |
# File 'lib/midilib/io/midifile.rb', line 131 def sequence_number(num) end |
#sequencer_specific(type, msg) ⇒ Object
128 129 |
# File 'lib/midilib/io/midifile.rb', line 128 def sequencer_specific(type, msg) end |
#smpte(hour, min, sec, frame, fract) ⇒ Object
143 144 |
# File 'lib/midilib/io/midifile.rb', line 143 def smpte(hour, min, sec, frame, fract) end |
#start_track(bytes_to_be_read) ⇒ Object
95 96 |
# File 'lib/midilib/io/midifile.rb', line 95 def start_track(bytes_to_be_read) end |
#sysex(msg) ⇒ Object
122 123 |
# File 'lib/midilib/io/midifile.rb', line 122 def sysex(msg) end |
#tempo(microsecs) ⇒ Object
146 147 |
# File 'lib/midilib/io/midifile.rb', line 146 def tempo(microsecs) end |
#text(type, msg) ⇒ Object
134 135 |
# File 'lib/midilib/io/midifile.rb', line 134 def text(type, msg) end |
#time_signature(numer, denom, clocks, qnotes) ⇒ Object
140 141 |
# File 'lib/midilib/io/midifile.rb', line 140 def time_signature(numer, denom, clocks, qnotes) end |
#write16(val) ⇒ Object
Write a sixteen-bit value.
408 409 410 411 412 |
# File 'lib/midilib/io/midifile.rb', line 408 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.
415 416 417 418 419 420 421 |
# File 'lib/midilib/io/midifile.rb', line 415 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.
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/midilib/io/midifile.rb', line 424 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 |