1 /* MidiFileReader.java -- Read MIDI files.
2    Copyright (C) 2006, 2012 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 package gnu.javax.sound.midi.file;
39 
40 import java.io.DataInputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.net.URL;
46 
47 import javax.sound.midi.InvalidMidiDataException;
48 import javax.sound.midi.MetaMessage;
49 import javax.sound.midi.MidiEvent;
50 import javax.sound.midi.MidiFileFormat;
51 import javax.sound.midi.MidiMessage;
52 import javax.sound.midi.Sequence;
53 import javax.sound.midi.ShortMessage;
54 import javax.sound.midi.SysexMessage;
55 import javax.sound.midi.Track;
56 
57 /**
58  * A MIDI file reader.
59  *
60  * This code reads MIDI file types 0 and 1.
61  *
62  * There are many decent documents on the web describing the MIDI file
63  * format.  I didn't bother looking for the official document.  If it
64  * exists, I'm not even sure if it is freely available.  We should
65  * update this comment if we find out anything helpful here.
66  *
67  * @author Anthony Green (green@redhat.com)
68  *
69  */
70 public class MidiFileReader extends javax.sound.midi.spi.MidiFileReader
71 {
72   /* Get the MidiFileFormat for the given input stream.
73    * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.io.InputStream)
74    */
getMidiFileFormat(InputStream in)75   public MidiFileFormat getMidiFileFormat(InputStream in)
76     throws InvalidMidiDataException, IOException
77   {
78     DataInputStream din;
79     if (in instanceof DataInputStream)
80       din = (DataInputStream) in;
81     else
82       din = new DataInputStream(in);
83 
84     int type, ntracks, division, resolution, bytes;
85     float divisionType;
86 
87     if (din.readInt() != 0x4d546864) // "MThd"
88       throw new InvalidMidiDataException("Invalid MIDI chunk header.");
89 
90     bytes = din.readInt();
91     if (bytes < 6)
92       throw new
93         InvalidMidiDataException("Invalid MIDI chunk header length: " + bytes);
94 
95     type = din.readShort();
96     if (type < 0 || type > 2)
97       throw new
98         InvalidMidiDataException("Invalid MIDI file type value: " + type);
99 
100     ntracks = din.readShort();
101     if (ntracks <= 0)
102       throw new
103         InvalidMidiDataException("Invalid number of MIDI tracks: " + ntracks);
104 
105     division = din.readShort();
106     if ((division & 0x8000) != 0)
107       {
108         division = -((division >>> 8) & 0xFF);
109         switch (division)
110           {
111           case 24:
112             divisionType = Sequence.SMPTE_24;
113             break;
114 
115           case 25:
116             divisionType = Sequence.SMPTE_25;
117             break;
118 
119           case 29:
120             divisionType = Sequence.SMPTE_30DROP;
121             break;
122 
123           case 30:
124             divisionType = Sequence.SMPTE_30;
125             break;
126 
127           default:
128             throw new
129               InvalidMidiDataException("Invalid MIDI frame division type: "
130                                        + division);
131           }
132         resolution = division & 0xff;
133       }
134     else
135       {
136         divisionType = Sequence.PPQ;
137         resolution = division & 0x7fff;
138       }
139 
140     // If we haven't read every byte in the header now, just skip the rest.
141     din.skip(bytes - 6);
142 
143     return new ExtendedMidiFileFormat(type, divisionType, resolution,
144                                       MidiFileFormat.UNKNOWN_LENGTH,
145                                       MidiFileFormat.UNKNOWN_LENGTH, ntracks);
146   }
147 
148   /* Get the MidiFileFormat from the given URL.
149    * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.net.URL)
150    */
getMidiFileFormat(URL url)151   public MidiFileFormat getMidiFileFormat(URL url)
152     throws InvalidMidiDataException, IOException
153   {
154     InputStream is = url.openStream();
155     try
156       {
157         return getMidiFileFormat(is);
158       }
159     finally
160       {
161         is.close();
162       }
163   }
164 
165   /* Get the MidiFileFormat from the given file.
166    * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.io.File)
167    */
getMidiFileFormat(File file)168   public MidiFileFormat getMidiFileFormat(File file)
169     throws InvalidMidiDataException, IOException
170   {
171     InputStream is = new FileInputStream(file);
172     try
173       {
174         return getMidiFileFormat(is);
175       }
176     finally
177       {
178         is.close();
179       }
180   }
181 
182   /* Get the MIDI Sequence found in this input stream.
183    * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.io.InputStream)
184    */
getSequence(InputStream is)185   public Sequence getSequence(InputStream is) throws InvalidMidiDataException,
186     IOException
187   {
188     MidiDataInputStream din = new MidiDataInputStream(is);
189     ExtendedMidiFileFormat mff = (ExtendedMidiFileFormat) getMidiFileFormat(din);
190 
191     Sequence seq = new Sequence(mff.getDivisionType(), mff.getResolution());
192 
193     int ntracks = mff.getNumberTracks();
194 
195     while (ntracks-- > 0)
196       {
197         Track track = seq.createTrack();
198         int Mtrk = din.readInt();
199         if (Mtrk != 0x4d54726b)
200           throw new InvalidMidiDataException("Invalid MIDI track header.");
201         din.readInt(); // length
202 
203         int runningStatus = -1;
204         int click = 0;
205 
206         // Set this to true when we've hit an End of Track meta event.
207         boolean done = false;
208 
209         // Read all events.
210         while (! done)
211           {
212             MidiMessage mm;
213             int dtime = din.readVariableLengthInt();
214             click += dtime;
215 
216             int sbyte = din.readUnsignedByte();
217 
218             if (sbyte < 0xf0)
219               {
220                 ShortMessage sm;
221                 switch (sbyte & 0xf0)
222                   {
223                   case ShortMessage.NOTE_OFF:
224                   case ShortMessage.NOTE_ON:
225                   case ShortMessage.POLY_PRESSURE:
226                   case ShortMessage.CONTROL_CHANGE:
227                   case ShortMessage.PITCH_BEND:
228                   case ShortMessage.SONG_POSITION_POINTER:
229                     sm = new ShortMessage();
230                     sm.setMessage(sbyte, din.readByte(), din.readByte());
231                     runningStatus = sbyte;
232                     break;
233 
234                   case ShortMessage.PROGRAM_CHANGE:
235                   case ShortMessage.CHANNEL_PRESSURE:
236                   case ShortMessage.SONG_SELECT:
237                   case 0xF5: // FIXME: unofficial bus select. Not in spec??
238                     sm = new ShortMessage();
239                     sm.setMessage(sbyte, din.readByte(), 0);
240                     runningStatus = sbyte;
241                     break;
242 
243                   case ShortMessage.TUNE_REQUEST:
244                   case ShortMessage.END_OF_EXCLUSIVE:
245                   case ShortMessage.TIMING_CLOCK:
246                   case ShortMessage.START:
247                   case ShortMessage.CONTINUE:
248                   case ShortMessage.STOP:
249                   case ShortMessage.ACTIVE_SENSING:
250                   case ShortMessage.SYSTEM_RESET:
251                     sm = new ShortMessage();
252                     sm.setMessage(sbyte, 0, 0);
253                     runningStatus = sbyte;
254                     break;
255 
256                   default:
257                     if (runningStatus != - 1)
258                       {
259                         switch (runningStatus & 0xf0)
260                           {
261                           case ShortMessage.NOTE_OFF:
262                           case ShortMessage.NOTE_ON:
263                           case ShortMessage.POLY_PRESSURE:
264                           case ShortMessage.CONTROL_CHANGE:
265                           case ShortMessage.PITCH_BEND:
266                           case ShortMessage.SONG_POSITION_POINTER:
267                             sm = new ShortMessage();
268                             sm.setMessage(runningStatus, sbyte, din.readByte());
269                             break;
270 
271                           case ShortMessage.PROGRAM_CHANGE:
272                           case ShortMessage.CHANNEL_PRESSURE:
273                           case ShortMessage.SONG_SELECT:
274                           case 0xF5: // FIXME: unofficial bus select. Not in
275                                      // spec??
276                             sm = new ShortMessage();
277                             sm.setMessage(runningStatus, sbyte, 0);
278                             continue;
279 
280                           case ShortMessage.TUNE_REQUEST:
281                           case ShortMessage.END_OF_EXCLUSIVE:
282                           case ShortMessage.TIMING_CLOCK:
283                           case ShortMessage.START:
284                           case ShortMessage.CONTINUE:
285                           case ShortMessage.STOP:
286                           case ShortMessage.ACTIVE_SENSING:
287                           case ShortMessage.SYSTEM_RESET:
288                             sm = new ShortMessage();
289                             sm.setMessage(runningStatus, 0, 0);
290                             continue;
291 
292                           default:
293                             throw new
294                               InvalidMidiDataException("Invalid Short MIDI Event: "
295                                                        + sbyte);
296                           }
297                       }
298                     else
299                       throw new
300                         InvalidMidiDataException("Invalid Short MIDI Event: "
301                                                  + sbyte);
302                   }
303                 mm = sm;
304               }
305             else if (sbyte == 0xf0 || sbyte == 0xf7)
306               {
307                 // System Exclusive event
308                 int slen = din.readVariableLengthInt();
309                 byte sysex[] = new byte[slen];
310                 din.readFully(sysex);
311                 SysexMessage sm = new SysexMessage();
312                 sm.setMessage(sbyte, sysex, slen);
313                 mm = sm;
314                 runningStatus = - 1;
315               }
316             else if (sbyte == 0xff)
317               {
318                 // Meta Message
319                 byte mtype = din.readByte();
320                 int mlen = din.readVariableLengthInt();
321                 byte meta[] = new byte[mlen];
322                 din.readFully(meta);
323                 MetaMessage metam = new MetaMessage();
324                 metam.setMessage(mtype, meta, mlen);
325                 mm = metam;
326 
327                 if (mtype == 0x2f) // End of Track
328                   done = true;
329 
330                 runningStatus = - 1;
331               }
332             else
333               {
334                 throw new InvalidMidiDataException("Invalid status byte: "
335                                                    + sbyte);
336               }
337 
338             track.add(new MidiEvent(mm, click));
339           }
340       }
341 
342     return seq;
343   }
344 
345   /* Get the MIDI Sequence found at the given URL.
346    * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.net.URL)
347    */
getSequence(URL url)348   public Sequence getSequence(URL url) throws InvalidMidiDataException,
349     IOException
350   {
351     InputStream is = url.openStream();
352     try
353       {
354         return getSequence(is);
355       }
356     finally
357       {
358         is.close();
359       }
360   }
361 
362   /* Get the MIDI Sequence found in the given file.
363    * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.io.File)
364    */
getSequence(File file)365   public Sequence getSequence(File file) throws InvalidMidiDataException,
366     IOException
367   {
368     InputStream is = new FileInputStream(file);
369     try
370       {
371         return getSequence(is);
372       }
373     finally
374       {
375         is.close();
376       }
377   }
378 }
379