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
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
-
#curr_ticks ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
-
#no_merge ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
-
#raw_data ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
-
#raw_time_stamp_data ⇒ Object
Raw data info.
-
#raw_var_num_data ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
-
#skip_init ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
-
#ticks_so_far ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped.
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.
29 30 31 32 33 34 35 |
# 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 |
Instance Attribute Details
#bytes_to_be_read ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def bytes_to_be_read @bytes_to_be_read end |
#curr_ticks ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def curr_ticks @curr_ticks end |
#no_merge ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def no_merge @no_merge end |
#raw_data ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def raw_data @raw_data end |
#raw_time_stamp_data ⇒ Object
Raw data info
27 28 29 |
# File 'lib/midilib/io/midifile.rb', line 27 def raw_time_stamp_data @raw_time_stamp_data end |
#raw_var_num_data ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def raw_var_num_data @raw_var_num_data end |
#skip_init ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 def skip_init @skip_init end |
#ticks_so_far ⇒ Object
Current time, from delta-time in MIDI file # Number of delta-time ticks so far # Counts number of bytes expected # true means continued sysex are not collapsed # true if initial garbage should be skipped
24 25 26 |
# File 'lib/midilib/io/midifile.rb', line 24 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.
268 269 270 |
# 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) ⇒ Object
Handle a channel message (note on, note off, etc.)
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/midilib/io/midifile.rb', line 309 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#{format('%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 |
#get_bytes(n) ⇒ Object
Return the next n
bytes from @io as an array.
57 58 59 60 61 |
# File 'lib/midilib/io/midifile.rb', line 57 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.
51 52 53 54 |
# File 'lib/midilib/io/midifile.rb', line 51 def getc @bytes_to_be_read -= 1 @io.readbyte end |
#handle_arbitrary(msg) ⇒ Object
Copy message into raw data array, then call arbitrary().
344 345 346 347 |
# File 'lib/midilib/io/midifile.rb', line 344 def handle_arbitrary(msg) @raw_data = msg.dup arbitrary(msg) end |
#handle_sysex(msg) ⇒ Object
Copy message into raw data array, then call sysex().
338 339 340 341 |
# File 'lib/midilib/io/midifile.rb', line 338 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.
273 274 275 276 277 278 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 |
# File 'lib/midilib/io/midifile.rb', line 273 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)) 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.
434 435 436 |
# File 'lib/midilib/io/midifile.rb', line 434 def msg @msg_buf.dup end |
#msg_add(c) ⇒ Object
Add a byte to the current message buffer.
416 417 418 |
# File 'lib/midilib/io/midifile.rb', line 416 def msg_add(c) @msg_buf << c end |
#msg_init ⇒ Object
Initialize the internal message buffer.
429 430 431 |
# File 'lib/midilib/io/midifile.rb', line 429 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).
422 423 424 425 426 |
# 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) ⇒ 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.
350 351 352 353 354 |
# File 'lib/midilib/io/midifile.rb', line 350 def read16 val = (getc << 8) + getc val = -(val & 0x7fff) if (val & 0x8000).nonzero? val end |
#read32 ⇒ Object
Read and return a 32-bit value.
357 358 359 360 361 362 |
# 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) ⇒ Object
The only public method. Each MIDI event in the file causes a method to be called.
39 40 41 42 43 44 45 46 47 |
# 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 ⇒ Object
Read a header chunk.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# 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) ⇒ 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.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# 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 ⇒ Object
Read a track chunk.
187 188 189 190 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 |
# 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. (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 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 ⇒ Object
Read a varlen value.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# 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) ⇒ 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.
383 384 385 386 387 |
# 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) ⇒ Object
Write a 32-bit value.
390 391 392 393 394 395 396 |
# 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) ⇒ Object
Write a variable length value.
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
# 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 |