DixShtix

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
0FF 5804 04 02 24 084 bytes: 4/4 time, 24 MIDI clocks/click, 8 32nd notes/24 MIDI clocks
0FF 5103 5000003 bytes: 500,000 5sec per quarter-note
0C05Ch. 1, Program Change 5
0C05Ch. 1, Program Change 5
0C146Ch. 2, Program Change 46
0C270Ch. 3, Program Change 70
09248 96Ch. 3 Note On C2, forte
09260 96Ch. 3 Note On C3, forte
969167 64Ch. 2 Note On G3, mezzo-forte
969076 32Ch. 1 Note On E4, piano
1928248 64Ch. 3 Note Off C2, standard
08260 64Ch. 3 Note Off C3, standard
08167 64Ch. 2 Note Off G3, standard
08076 64Ch. 1 Note Off E4, standard
0FF 2F00Track 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
00FF 58 04 04 02 18 08time signature
00FF 51 03 07 A1 20tempo
00C0 05
00C1 2E
00C2 46
0092 30 60
003C 60running status
6091 43 40
6090 4C 20
81 4082 30 40two-byte delta-time
003C 40running status
0081 43 40
0080 4C 40
00FF 2F 00end 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
00FF 58 04 04 02 18 08time signature
00FF 51 03 07 A1 20 tempo
83 00FF 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
00C0 05
81 404C 20
81 4000Running status: note on, velocity = 0
00FF 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
00C1 2E
6091 43 40
82 2043 00Running status
00FF 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
00C2 46
0092 30 60
003C 60Running Status
83 0030 00Two-byte delta time, Running status
003C 00Running status
00FF 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

Field Summary
(package private)  int format
           
(package private)  int frames_per_sec
           
static byte[] groupID
          IFF group id for RMID files.
static byte[] iChunkID
          Initial Chunk ID.
static int iChunkLen
          Guaranteed Initial Chunk Length.
static byte[] initialID
          IFF first chunk id for RMID files.
(package private)  boolean rmid_format
           
(package private)  int ticks_per_qnote
           
(package private)  java.util.Vector tracks
           
static byte[] typeID
          IFF type id for RMID files.
 
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
 

Field Detail

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.

Constructor Detail

Opus

public Opus()
Initializer.

Opus

public Opus(Opus old,
            int newFormat)
Change format of a midi file. Not done.
Method Detail

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 IDFormat Description
0the file contains a single multi-channel track
1the file contains one or more simultaneous tracks (or MIDI outputs) of a sequence
2the 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.

DixShtix