midilib¶ ↑
midilib is a pure Ruby MIDI
library useful for reading and writing standard MIDI
files and manipulating MIDI
event data.
The GitHub project page and Web site of midilib is github.com/jimm/midilib and the RubyGems.org page is rubygems.org/gems/midilib, where you can also find all the RDoc documentation.
midilib is compatible with Ruby 2.6 and higher.
Dependencies¶ ↑
midilib does not require any other packages. The test suite in the tests directory requires the testing framework TestUnit, which comes with Ruby 1.8 and later and can also be found in the Ruby Application Archive (raa.ruby-lang.org).
To rebuild the gem or RDocs or run the tests easily, you can use the Rakefile which requires Rake (rake.rubyforge.org).
Installation¶ ↑
RubyGems Installation¶ ↑
To install midilib as a gem, type
% gem install midilib
or, if you already have a previous version, use
% gem update midilib
You may need root privileges to install or update the gem.
Manual Installation¶ ↑
After downloading and expanding the archive, you can install midilib with the command
% ruby install.rb
(or)
% ruby install.rb --install-dir=my_directory
You may need root privileges to install.
Testing¶ ↑
% rake test
runs all of the tests in the test directory.
Overview¶ ↑
midilib can read and write MIDI
file formats 0 (one single track) or 1 multiple tracks. By default, it writes format 1 which is the most common format. MIDI
file format 2 is not yet supported.
MIDI::Sequence
¶ ↑
A sequence contains a collection of tracks and global information like the sequence’s pulses per quarter note (ppqn) and time signature.
The first track in a sequence is special; it holds meta-events like tempo and sequence name. Don’t put any notes in this track.
MIDI::Sequence
also contains some convenience methods that let you set and retrieve the sequence’s name, the time signature, and to retrieve the first tempo event’s beats-per-minute value.
Normally instances of MIDI::IO::SeqReader
and MIDI::IO::SeqWriter
are used when a sequence reads itself from or writes itself to a MIDI
file. You can change that by setting a sequence’s reader_class or writer_class attributes. Instances of the classes contained in those attributes are created and used whenever the sequence reads or writes itself.
MIDI::Track
¶ ↑
A track contains an array of events.
When you modify the events
array, make sure to call recalc_times so each event gets its time_from_start
recalculated. You don’t have to do that after every event you add; just remember to do so before using the track in a way that expects the list of events to be ordered correctly.
A Track also holds a bit mask that specifies the channels used by the track. This bit mask is set when the track is read from the MIDI
file by a SeqReader but is not kept up to date by any other methods. Specifically, if you add events to a track at any other time, the bit mask will not be updated.
When a Track is read from a MIDI
file, a MIDI::META_TRACK_END event is added to the end if there isn’t one in the file already. When a Track is written to a MIDI
file, a MIDI::META_TRACK_END event is always output even if the Track does not have one.
The Track#merge method ensures that there is only one MIDI::META_TRACK_END event after the merge and that it’s at its proper place at the end of the list of events. It does so by calling Track#ensure_track_end_meta_event.
MIDI::Measure
¶ ↑
This class contains information about a measure from the sequence. Measure data is based on the time signature information from the sequence and is not stored in the sequence itself.
MIDI::Measures
¶ ↑
The class MIDI::Sequence
method get_measures returns a MIDI::Measures
object. MIDI::Measures
is a subclass of Array. It is a specialized container for MIDI::Measure
objects, which can be use to map event times to measure numbers. Please note that this object has to be remade when events are deleted/added in the sequence.
MIDI::Measure
and MIDI::Measures
are brought to us by Jari Williamsson <jari.williamsson@mailbox.swipnet.se>, who also contributed some improvements to the MIDI::Event
and MIDI::Track
classes.
MIDI::Event
¶ ↑
Each event holds not only its delta time but also its time from the start of the track. The track is responsible for recalculating its events’ start times. You can call MIDI::Track#recalc_times
to do so.
Subclasses of MIDI::Event
implement the various MIDI
messages such as note on and off, controller values, system exclusive data, and realtime bytes.
MIDI::Realtime
events have delta values and start times, just like all the other midilib event types do. (MIDI
real time status bytes don’t have delta times, but this way we can record when in a track the realtime byte was received and should be sent. This is useful for start/continue/stop events that control other devices, for example.) Note that when a MIDI::Realtime
event is written out to a MIDI
file, the delta time is not written.
MIDI::MetaEvent
events hold an array of bytes named ‘data’. Many meta events are string holders (text, lyric, marker, etc.) Though the ‘data’ value is always an array of bytes, MIDI::MetaEvent
helps with saving and accessing string. The MIDI::MetaEvent#data_as_str
method returns the data bytes as a string. When assigning to a meta event’s data, if you pass in a string it will get converted to an array of bytes.
How To Use midilib¶ ↑
The following examples show you how to use midilib to read, write, and manipulate MIDI
files and modify track events. See also the files in the examples directory, which are described below.
Reading a MIDI
File¶ ↑
To read a MIDI
file, create a MIDI::Sequence
object and call its read method, passing in an IO object.
The read method takes an optional block. If present, the block is called once after each track has finished being read. Each time, it is passed the track object, the total number of tracks and the number of the current track that has just been read. This is useful for notifying the user of progress, for example by updating a GUI progress bar.
require 'midilib/io/seqreader' # Create a new, empty sequence. seq = MIDI::Sequence.new() # Read the contents of a MIDI file into the sequence. File.open('my_midi_file.mid', 'rb') { | file | seq.read(file) { | track, num_tracks, i | # Print something when each track is read. puts "read track #{i} of #{num_tracks}" } }
Writing a MIDI
File¶ ↑
To write a MIDI
file, call the write method, passing in an IO object.
require 'midilib/io/seqwriter' # Start with a sequence that has something worth saving. seq = read_or_create_seq_we_care_not_how() # Write the sequence to a MIDI file. File.open('my_output_file.mid', 'wb') { | file | seq.write(file) }
Editing a MIDI
File¶ ↑
Combining the last two examples, here is a script that reads a MIDI
file, transposes some events, and writes the sequence out to a different file. This is a useful template for programatically manipulating MIDI
data.
This code transposes all of the note events (note on, note off, and poly pressure) on channel 5 down one octave.
Transposing One Channel¶ ↑
require 'midilib/io/seqreader' require 'midilib/io/seqwriter' # Create a new, empty sequence. seq = MIDI::Sequence.new() # Read the contents of a MIDI file into the sequence. File.open('my_input_file.mid', 'rb') { | file | seq.read(file) { | track, num_tracks, i | # Print something when each track is read. puts "read track #{i} of #{num_tracks}" } } # Iterate over every event in every track. seq.each { | track | track.each { | event | # If the event is a note event (note on, note off, or poly # pressure) and it is on MIDI channel 5 (channels start at # 0, so we use 4), then transpose the event down one octave. if MIDI::NoteEvent === event && event.channel == 4 event.note -= 12 end } } # Write the sequence to a MIDI file. File.open('my_output_file.mid', 'wb') { | file | seq.write(file) }
Manipulating tracks¶ ↑
If you modify a track’s list of events directly, don’t forget to call MIDI::Track#recalc_times
when you are done.
track.events[42, 1] = array_of_events track.events << an_event track.merge(array_of_events) track.recalc_times
Calculating delta times¶ ↑
A few methods in MIDI::Sequence
make it easier to calculate the delta times that represent note lengths. MIDI::Sequence#length_to_delta
takes a note length (a multiple of a quarter note) and returns the delta time given the sequence’s current ppqn (pulses per quarter note) setting. 1 is a quarter note, 1.0/32.0 is a 32nd note (use floating-point numbers to avoid integer rounding), 1.5 is a dotted quarter, etc. See the documentation for that method for more information.
MIDI::Sequence#note_to_length
takes a note name and returns a length value (again, as a multiple of a quarter note). Legal note names are those found in MIDI::Sequence::NOTE_TO_LENGTH, and may begin with “dotted” and/or end with “triplet”. For example, “whole”, “sixteenth”, “32nd”, “quarter triplet”, “dotted 16th”, and “dotted 8th triplet” are all legal note names.
Finally, MIDI::Sequence#note_to_delta
takes a note name and returns a delta time. It does this by calling note_to_length, then passing the result to length_to_delta.
Example Scripts¶ ↑
Here are short descriptions of each of the examples found in the examples directory.
-
examples/from_scratch.rb shows you how to create a new sequence from scratch and save it to a
MIDI
file. It creates a file called ‘from_scratch.mid’. -
examples/seq2text.rb dumps a
MIDI
file as text. It reads in a sequence and uses the to_s method of each event. -
examples/reader2text.rb dumps a
MIDI
file as text. It subclasses MIDI::SeqReader instead of creating a sequence containing tracks and events. -
examples/transpose.rb transposes all note events (note on, note off, poly pressure) on a specified channel by a specified amount.
-
There is also one
MIDI
file, examples/NoFences.mid. It is a little pop ditty I wrote. The instruments in this file use GeneralMIDI
patch numbers and drum note assignments. Since I don’t normally use GM patches, the sounds used here are at best approximations of the sounds I use.
Resources¶ ↑
The Ruby Web site (www.ruby-lang.org/en/index.html) contains an introduction to Ruby, the Ruby Application Archive (RAA) at raa.ruby-lang.org, and pointers to more information.
<cite>Programming Ruby, The Pragmatic Programmer’s Guide</cite>, by David Thomas and Andrew Hunt, is a well-written and practical introduction to Ruby. Its Web page at www.rubycentral.com/book also contains a wealth of Ruby information. Though the first edition book is available online, I encourage you to purchase a copy of the latest edition.
A description of the MIDI
file format can be found in a few places such as www.csie.ntu.edu.tw/~r92092/ref/midi/.
The MIDI
message reference at www.jimmenard.com/midi_ref.html describes the format of MIDI
commands.
To Do¶ ↑
Bugs¶ ↑
midilib does not handle tempo changes when calculating beats_per_minute
. See github.com/jimm/midilib/issues/8 which describes the issue. The tempo events are correctly handled when reading/writing/moving them around, it’s just the functions that answer questions about the current tempo that are wrong. See github.com/jimm/midilib/issues/8. The method beats_per_minute
and related methods like pulses_to_seconds
will have to take into account the possibility of more than one tempo event. They will probably have to take new arguments specifying where in the sequence the beats or pulses are being requested. For example we could have +beats_per_minute(at_seconds: 0.0, at_beat: 0.0)+ (where the two keyword args are mutually exclusive). Perhaps there should be separately named public methods like beats_per_minute_at_seconds
and beats_per_minute_at_beat
as well.
Features¶ ↑
-
print_decimal_numbers
andprint_channel_numbers_from_one
should be associated with sequence, or perhaps track, not event. -
Method to translate event’s time_from_start to number of milliseconds from start.
-
Swing quantizing. (Implied by list email from Carl Youngblood <carl.youngblood@gmail.com>)
-
Implement key signature in SeqReader.
-
Ignore unknown chunks in
MIDI
files. See theMIDI
file spec. -
Format 2 files(?).
Documentation¶ ↑
-
Write better docs.
Tests¶ ↑
-
Tests for Noah Thorp’s midilib bug fixes.
Support¶ ↑
-
Visit the forums, bug list, and mailing list pages at rubyforge.org/projects/midilib
-
Send email to Jim Menard at jim@jimmenard.com
-
Ask on the ruby-talk mailing list
Administrivia¶ ↑
- Author
-
Jim Menard (jim@jimmenard.com)
- Copyright
-
Copyright © 2003-2023 Jim Menard
- License
-
Distributed under the same license as Ruby.
Copying¶ ↑
midilib is copyrighted free software by Jim Menard and is released under the same license as Ruby. See the Ruby license at www.ruby-lang.org/en/LICENSE.txt.
midilib may be freely copied in its entirety providing this notice, all source code, all documentation, and all other files are included.
midilib is Copyright © 2003-2023 by Jim Menard.
The song “No Fences” contained in the MIDI
file examples/NoFences.mid is Copyright © 1992 by Jim Menard (jim@jimmenard.com). It may be freely used for non-commercial purposes as long as the author is given credit.
Warranty¶ ↑
This software is provided “as is” and without any express or implied warranties, including, without limitation, the implied warranties of merchantability and fitness for a particular purpose.