com.dixshtix.midi
Class Opus
java.lang.Object
|
+--com.dixshtix.midi.Opus
- public class Opus
- extends java.lang.Object
An Entire MIDI or RMID format file.
Theory
The class implements the specification for MIDI Files. The purpose of
MIDI Files is to provide a way of interchanging time-stamped MIDI data
between different programs on the same or different computers. One
of the primary design goals is compact representation, which makes it
very appropriate for disk-based file format, but which might make it
inappropriate for storing in memory for quick access by a sequencer
program. (It can be easily converted to a quickly-accessible format
on the fly as files are read in or written out.) It is not intended
to replace the normal file format of any program, though it could be
used for this purpose if desired.
MIDI Files contain one or more MIDI streams, with time information
for each event. Song, sequence, and track structures, tempo and
time signature information, are all supported. Track names and
other descriptive information may be stored with the MIDI data. This
format supports multiple tracks and multiple sequences so that if
the user of a program which supports multiple tracks intends to
move a file to another one, this format can allow that to happen.
This class implements the 8-bit binary data stream used in
the Standard MIDI files. The data can alternately be stored in a
binary file, nybblized, 7-bit-ized for efficient MIDI transmission,
converted to Hex ASCII, or translated symbolically to a printable
text file. This class only implements what's in the 8-bit stream.
It does not address how a MIDI File will be transmitted over MIDI. It
is the general feeling that a MIDI transmission protocol will be
developed for files in general and MIDI Files will use this scheme.
Example
As an example, MIDI Files for the following excerpt are shown below.
First, a format 0 file is shown, with all information intermingled;
then, a format 1 file is shown with all data separated into four tracks:
one for tempo and time signature, and three for the notes. A resolution
of 96 "ticks" per quarter note is used. A time signature of 4/4 and a
tempo of 120, though implied, are explicitly stated.
The contents of the MIDI stream represented by this example are broken
down here:
Delta Time(decimal) | Event Code (hex) | Other Bytes (decimal) | Comment |
0 | FF 58 | 04 04 02 24 08 | 4 bytes: 4/4 time, 24 MIDI clocks/click, 8 32nd notes/24 MIDI clocks |
0 | FF 51 | 03 500000 | 3 bytes: 500,000 5sec per quarter-note |
0 | C0 | 5 | Ch. 1, Program Change 5 |
0 | C0 | 5 | Ch. 1, Program Change 5 |
0 | C1 | 46 | Ch. 2, Program Change 46 |
0 | C2 | 70 | Ch. 3, Program Change 70 |
0 | 92 | 48 96 | Ch. 3 Note On C2, forte |
0 | 92 | 60 96 | Ch. 3 Note On C3, forte |
96 | 91 | 67 64 | Ch. 2 Note On G3, mezzo-forte |
96 | 90 | 76 32 | Ch. 1 Note On E4, piano |
192 | 82 | 48 64 | Ch. 3 Note Off C2, standard |
0 | 82 | 60 64 | Ch. 3 Note Off C3, standard |
0 | 81 | 67 64 | Ch. 2 Note Off G3, standard |
0 | 80 | 76 64 | Ch. 1 Note Off E4, standard |
0 | FF 2F | 00 | Track End |
The entire format 0 MIDI file contents in hex follow. First, the
header chunk:
Hexadecimal Data | Description |
4D 54 68 64 | MThd |
00 00 00 06 | chunk length |
00 00 | format 0 |
00 01 | one track |
00 60 | 96 per quarter-note |
Then, the track chunk. Its header, followed by the events (notice
that running status is used in places):
Hexadecimal Data | Description |
4D 54 72 6B | MTrk |
00 00 00 3B | chunk length (59) |
Delta-time | Event | Comments |
00 | FF 58 04 04 02 18 08 | time signature |
00 | FF 51 03 07 A1 20 | tempo |
00 | C0 05 |
00 | C1 2E |
00 | C2 46 |
00 | 92 30 60 |
00 | 3C 60 | running status |
60 | 91 43 40 |
60 | 90 4C 20 |
81 40 | 82 30 40 | two-byte delta-time |
00 | 3C 40 | running status |
00 | 81 43 40 |
00 | 80 4C 40 |
00 | FF 2F 00 | end of track |
A format 1 representation of the file is slightly different. Its
header chunk:
Hexadecimal Data | Description |
4D 54 68 64 | MThd |
00 00 00 06 | chunk length |
00 01 | format 1 |
00 04 | four tracks |
00 60 | 96 per quarter-note |
First, the track chunk for the time signature/tempo track. Its header,
followed by the events:
Hexadecimal Data | Description |
4D 54 72 6B | MTrk |
00 00 00 14 | chunk length (20) |
Delta-time | Event | Comments |
00 | FF 58 04 04 02 18 08 | time signature |
00 | FF 51 03 07 A1 20 | tempo |
83 00 | FF 2F 00 | end of track |
Then, the track chunk for the first music track. The MIDI convention
for note on/off running status is used in this example:
Hexadecimal Data | Description |
4D 54 72 6B | MTrk |
00 00 00 10 | chunk length (16) |
Delta-time | Event | Comments |
00 | C0 05 |
81 40 | 4C 20 |
81 40 | 00 | Running status: note on, velocity = 0 |
00 | FF 2F 00 | end of track |
Then, the track chunk for the second music track:
Hexadecimal Data | Description |
4D 54 72 6B | MTrk |
00 00 00 0F | chunk length (15) |
Delta-time | Event | Comments |
00 | C1 2E |
60 | 91 43 40 |
82 20 | 43 00 | Running status |
00 | FF 2F 00 | end of track |
Then, the track chunk for the third music track:
Hexadecimal Data | Description |
4D 54 72 6B | MTrk |
00 00 00 15 | chunk length (21) |
Delta-time | Event | Comments |
00 | C2 46 |
00 | 92 30 60 |
00 | 3C 60 | Running Status |
83 00 | 30 00 | Two-byte delta time, Running status |
00 | 3C 00 | Running status |
00 | FF 2F 00 | end of track |
RMID Files
The method of saving data in chunks (ie, where the data is preceded by
an 8 byte header consisting of a 4 char ID and a 32-bit size field) is
the basis for Interchange File Format. You should now read the article
About Interchange File Format for background information.
As mentioned, MIDI File format is a "broken" IFF. It lacks a file
header at the start of the file. One bad thing about this is that a
standard IFF parsing routine will choke on a MIDI file (because it
will expect the first 12 bytes to be the group ID, file size, and type
ID fields). In order to fix the MIDI File format so that it strictly
adheres to IFF, Microsoft simply made up a 12-byte header that is
prepended to MIDI files, and thereby came up with the RMID format. An
RMID file begins with the group ID (4 ASCII chars) of 'R', 'I', 'F',
'F', followed by the 32-bit file size field, and then the type ID of 'R',
'M', 'I', 'D'. Then, the chunks of a MIDI file follow (ie, the MThd
and MTrk chunks). If you chop off the first 12 bytes of an RMID file,
then you end up with a standard MIDI file.
Note that chunks within a MIDI file are not padded out (with an extra
0 byte) to an even number of bytes. I don't know as if the RMID format
corrects this aberration of the MIDI file format too.
- Version:
- 0.1
- Author:
- Richard C. Penner II
Constructor Summary |
Opus()
Initializer. |
Opus(Opus old,
int newFormat)
Change format of a midi file. |
Method Summary |
void |
addTrack(Track value)
Adds a track to the Opus. |
static boolean |
bytesSame(byte[] a,
byte[] b)
Utility comparison function. |
int |
getFormat()
Accessor. |
int |
getFrames()
Accessor. |
int |
getNTracks()
Accessor method. |
boolean |
getRMID()
Accessor to file type. |
int |
getTicks()
Accessor to granularity of time. |
Track |
getTrack(int index)
Accessor method. |
Track[] |
getTracks()
Accessor method. |
static Opus |
read(java.io.InputStream input)
Read a MIDI file from an InputStream. |
void |
readTracks(java.io.InputStream input)
Read zero or more MIDI tracks, until end of stream. |
void |
readTracks(java.io.InputStream input,
int remaining)
Read zero or more MIDI tracks from an InputStream. |
void |
setFormat(int value)
|
void |
setFrames(int value)
|
void |
setRMID(boolean value)
|
void |
setTicks(int value)
Changes the granularity of time. |
void |
setTracks(Track[] value)
Bulk assignment method. |
java.lang.String |
toString()
|
void |
write(java.io.OutputStream out)
Write a MIDI file to an OutputStream. |
void |
write(java.io.OutputStream out,
boolean isRMID)
Write a MIDI file to an OutputStream. |
void |
write(java.io.OutputStream out,
boolean isRMID,
int desiredFormat)
Write a MIDI file to an OutputStream. |
Methods inherited from class java.lang.Object |
, clone, equals, finalize, getClass, hashCode, notify, notifyAll, registerNatives, wait, wait, wait |
rmid_format
boolean rmid_format
format
int format
frames_per_sec
int frames_per_sec
ticks_per_qnote
int ticks_per_qnote
tracks
java.util.Vector tracks
groupID
public static final byte[] groupID
- IFF group id for RMID files.
typeID
public static final byte[] typeID
- IFF type id for RMID files.
initialID
public static final byte[] initialID
- IFF first chunk id for RMID files.
iChunkID
public static final byte[] iChunkID
- Initial Chunk ID.
Equivalent to Chunk.kMThd.
- See Also:
Chunk.kMThd
iChunkLen
public static final int iChunkLen
- Guaranteed Initial Chunk Length.
Note: Input logic which depends on this value is, in fact, faulty.
This value is here as a contract of output behavior.
Opus
public Opus()
- Initializer.
Opus
public Opus(Opus old,
int newFormat)
- Change format of a midi file.
Not done.
toString
public java.lang.String toString()
- Overrides:
toString
in class java.lang.Object
getRMID
public boolean getRMID()
- Accessor to file type.
setRMID
public void setRMID(boolean value)
getFormat
public int getFormat()
- Accessor. Should be 0 or 1 for most data.
setFormat
public void setFormat(int value)
getFrames
public int getFrames()
- Accessor. If not 0, should be 24,25,29,30 frames per sec.
setFrames
public void setFrames(int value)
getTicks
public int getTicks()
- Accessor to granularity of time.
setTicks
public void setTicks(int value)
- Changes the granularity of time.
getNTracks
public int getNTracks()
- Accessor method.
getTracks
public Track[] getTracks()
- Accessor method.
setTracks
public void setTracks(Track[] value)
- Bulk assignment method.
getTrack
public Track getTrack(int index)
- Accessor method.
addTrack
public void addTrack(Track value)
- Adds a track to the Opus.
bytesSame
public static boolean bytesSame(byte[] a,
byte[] b)
- Utility comparison function.
This compares two arrays of bytes to make sure they are the same.
It does not attempt to order the bytes.
readTracks
public void readTracks(java.io.InputStream input,
int remaining)
throws java.io.IOException
- Read zero or more MIDI tracks from an InputStream.
CHUNKS
MIDI files are made up of chunks. Each chunk has a
4-character type and a 32-bit length, which is the number of
bytes in the chunk. This structure allows future chunk types
to be designed which may be easily be ignored if encountered
by a program written before the chunk type is introduced.
Your programs should EXPECT alien chunks and treat them as
if they weren't there.
Each chunk begins with a 4-character ASCII type. It is followed
by a 32-bit length, most significant byte first (a length of 6
is stored as 00 00 00 06). This length refers to
the number of bytes of data which follow: the eight bytes
of type and length are not included. Therefore, a chunk with
a length of 6 would actually occupy 14 bytes in the disk file.
This chunk architecture is similar to that used by Electronic
Arts' IFF format, and the chunks described herein could easily
be placed in an IFF file. The MIDI File itself is not an
IFF file: it contains no nested chunks, and chunks are not
constrained to be an even number of bytes long. Converting it
to an IFF file is as easy as padding odd length chunks, and
sticking the whole thing inside a FORM chunk.
TYPES OF CHUNKS
The Standard Midi File Spec defines two types of chunks:
a header chunk and a track chunks. A header chunk provides a
minimal amount of information pertaining to the entire MIDI
file. A track chunk contains a sequential stream of MIDI data
which may contain information for up to 16 MIDI channels. The
concepts of multiple tracks, multiple MIDI outputs, patterns,
sequences, and songs may all be implemented using several
track chunks.
A MIDI File always starts with a header chunk, and is followed
by one or more track chunks.
THE HEADER CHUNK
THE TRACK CHUNK
- Parameters:
input
- Stream which points at the Track Chunks.remaining
- Number of bytes left to be read, or -1
if indefinite.
readTracks
public void readTracks(java.io.InputStream input)
throws java.io.IOException
- Read zero or more MIDI tracks, until end of stream.
- Parameters:
input
- Stream which points at the Track Chunks.remaining
- Number of bytes left to be read, or -1
if indefinite.
write
public void write(java.io.OutputStream out)
throws java.io.IOException
- Write a MIDI file to an OutputStream.
Not done.
write
public void write(java.io.OutputStream out,
boolean isRMID)
throws java.io.IOException
- Write a MIDI file to an OutputStream.
Not done.
write
public void write(java.io.OutputStream out,
boolean isRMID,
int desiredFormat)
throws java.io.IOException
- Write a MIDI file to an OutputStream.
Not done.
read
public static Opus read(java.io.InputStream input)
throws java.io.IOException
- Read a MIDI file from an InputStream.
FILE TYPES
To any file system, a MIDI File is simply a series of 8-bit
bytes. On the Macintosh, this byte stream is stored in the data
fork of a file (with file type 'MIDI'), or on the Clipboard
(with data type 'MIDI'). Most other computers store 8-bit
byte streams in files -- naming or storage conventions
for those computers will be defined as required.
This function also reads PC '.rmi' files as they are very
similar to the MIDI Standard Format.
BACKGROUND
MIDI files contain one or more MIDI streams, with time
information for each event. Song, sequence, and track structures,
tempo and time signature information, are all supported. Track
names and other descriptive information may be stored with the
MIDI data. This format supports multiple tracks and multiple
sequences so that if the user of a program which supports
multiple tracks intends to move a file to another one, this
format can allow that to happen.
CHUNKS
MIDI files are made up of chunks. Each chunk has a
4-character type and a 32-bit length, which is the number of
bytes in the chunk. This structure allows future chunk types
to be designed which may be easily be ignored if encountered
by a program written before the chunk type is introduced.
Your programs should EXPECT alien chunks and treat them as
if they weren't there.
Each chunk begins with a 4-character ASCII type. It is followed
by a 32-bit length, most significant byte first (a length of 6
is stored as 00 00 00 06). This length refers to
the number of bytes of data which follow: the eight bytes
of type and length are not included. Therefore, a chunk with
a length of 6 would actually occupy 14 bytes in the disk file.
This chunk architecture is similar to that used by Electronic
Arts' IFF format, and the chunks described herein could easily
be placed in an IFF file. The MIDI File itself is not an
IFF file: it contains no nested chunks, and chunks are not
constrained to be an even number of bytes long. Converting it
to an IFF file is as easy as padding odd length chunks, and
sticking the whole thing inside a FORM chunk.
TYPES OF CHUNKS
The Standard Midi File Spec defines two types of chunks:
a header chunk and a track chunks. A header chunk provides a
minimal amount of information pertaining to the entire MIDI
file. A track chunk contains a sequential stream of MIDI data
which may contain information for up to 16 MIDI channels. The
concepts of multiple tracks, multiple MIDI outputs, patterns,
sequences, and songs may all be implemented using several
track chunks.
A MIDI File always starts with a header chunk, and is followed
by one or more track chunks.
THE HEADER CHUNK
The header chunk at the beginning of the file specifies
some basic information about the data in the file. The data
section contains three 16-bit words, stored high byte first
(contrary to the backwards world of Intel). Here's the syntax
of the complete chunk:
<chunk type> <length> <format> <ntrks> <division>
As described above, <chunk type> is the four ASCII
characters 'MThd'; <length> is a 32-bit representation
of the number 6 (high byte first). The first 16-bit word,
<format>, specifies the overall organization of the
file. Only three values of format are specified:
Defined SMF format IDs
Format ID | Format Description |
0 | the file contains a single multi-channel track |
1 | the file contains one or more simultaneous tracks (or MIDI outputs) of a sequence |
2 | the file contains one or more sequentially independent single-track patterns |
The next word, <ntrks>, is the number of track chunks in the
file. It will always be 1 for a format 0 file.
The third word, <division>, can take positive and
negative values. When <division> is positive, it is the
division of a quarter-note represented by the delta-times in
the file. This is known as Metrical Time, where the "clock"
is variable like the metronome markings in CMN (common music notation).
If <division> is negative, it represents the division
of a second represented by the delta-times in the file, so that
the track can represent events occurring in actual time instead
of metrical time. This is Time-Code based time.
Expanding on this, if the high bit is off, the fifteen
remaining bits are a measure of how fine grained the timing is
within a file. They form the number of delta-time "ticks" per
quarter note. If the <division> is 96, then 48 "ticks"
must pass between Note On and Note Off to form an eighth-note.
If the high bit of <division> is on, the first eight bits
are interpreted as the negative number of frames per second in a
SMPTE sense. The remaining 8 bits are interpreted as a positive
number, the number "ticks" per frame. The Negative SMPTE is
represented in the following way: the upper byte is one of the
four values -24, -25, -29, or -30, corresponding to the four
standard SMPTE and MIDI time code formats, and represents the
number of frames per second (29 represents "30 drop frame".
or 29.97 frames per second).
The second byte (stored positive) is the resolution within
a frame: typical values may be 4 (MIDI time code resolution),
8, 10, 80 (bit resolution), or 100. This system allows exact
specification of time-code-based tracks, but also allows
millisecond-based tracks by specifying 25 frames/sec and a
resolution of 40 units per frame.)
MORE ON FORMATS
Format 0, that is, one multi-channel track, is the most
interchangeable representation of data. One application of
MIDI files is a simple single-track player in a program which
needs to make synthesizers make sounds, but which is primarily
concerned with something else such as mixers or sound effect
boxes. It is very desirable to be able to produce such a format,
even if your program is track-based, in order to work with these
simple programs. On the other hand, perhaps someone will write
a format conversion from format 1 to format 0 which might be so
easy to use in some setting that it would save you the trouble
of putting it into your program.
Programs which support several simultaneous tracks
should be able to save and read data in format 1, a
vertically one-dimensional form, that is, as a collection of
tracks. Programs which support several independent patterns
should be able to save and read data in format 2, a horizontally
one-dimensional form. Providing these minimum capabilities will
ensure maximum interchangeability.
TEMPO MAPS
In a MIDI system with a computer and a SMPTE synchronizer
which uses Song Pointer and Timing Clock, tempo maps (which
describe the tempo throughout the track, and may also include
time signature information, so that the bar number may be
derived) are generally created on the computer. To use them
with the synchronizer, it is necessary to transfer them from
the computer. To make it easy for the synchronizer to extract
this data from a MIDI File, tempo information should always be
stored in the first MTrk chunk. For a format 0 file, the tempo
will be scattered through the track and the tempo map reader
should ignore the intervening events; for a format 1 file,
the tempo map must be stored as the first track. It is polite
to a tempo map reader to offer your user the ability to make
a format 0 file with just the tempo, unless you can use format 1.
MIDI files can express tempo and time signature, and they have
been chosen to do so for transferring tempo maps from one device
to another. For a format 0 file, the tempo will be scattered
through the track and the tempo map reader should ignore the
intervening events; for a format 1 file, the tempo map must
(starting in 0.04) be stored as the first track. It is polite
to a tempo map reader to offer your user the ability to make
a format 0 file with just the tempo, unless you can use format 1.
All MIDI files should specify tempo and time signature. If they
don't, the time signature is assumed to be 4/4, and the tempo
120 beats per minute.
CAVEAT PROGRAMMER
In format 0, these meta-events should occur at least at the
beginning of the single multi-channel track. In format 1, these
meta-events should be contained i| the first track. In format
2, each of the temporally independent patterns should contain
at least initial time signature and tempo information. We may
decide to define other format IDs to support other structures. A
program encountering an unknown format ID may still read other
MTrk chunks it finds from the file, as format 1 or 2, if its
user can make sense of them and arrange them into some other
structure if appropriate. Also, more parameters may be added
to the MThd chunk in the future: it is important to read and
honor the length, even if it is longer than 6.
- Parameters:
input
- A stream which is pointing at the beginning of
RMID or MIDI data.