1 /*
2  * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.media.sound;
27 
28 import java.io.EOFException;
29 import java.io.IOException;
30 import java.io.InputStream;
31 
32 import javax.sound.midi.InvalidMidiDataException;
33 import javax.sound.midi.MetaMessage;
34 import javax.sound.midi.MidiEvent;
35 import javax.sound.midi.MidiMessage;
36 import javax.sound.midi.MidiSystem;
37 import javax.sound.midi.MidiUnavailableException;
38 import javax.sound.midi.Receiver;
39 import javax.sound.midi.Sequence;
40 import javax.sound.midi.Track;
41 import javax.sound.sampled.AudioFileFormat.Type;
42 import javax.sound.sampled.AudioFormat;
43 import javax.sound.sampled.AudioInputStream;
44 import javax.sound.sampled.UnsupportedAudioFileException;
45 
46 /**
47  * MIDI File Audio Renderer/Reader.
48  *
49  * @author Karl Helgason
50  */
51 public final class SoftMidiAudioFileReader extends SunFileReader {
52 
53     private static final Type MIDI = new Type("MIDI", "mid");
54 
55     private static final AudioFormat format = new AudioFormat(44100, 16, 2,
56                                                               true, false);
57 
getAudioFileFormat(final Sequence seq)58     private static StandardFileFormat getAudioFileFormat(final Sequence seq) {
59         long totallen = seq.getMicrosecondLength() / 1000000;
60         long len = (long) (format.getFrameRate() * (totallen + 4));
61         return new StandardFileFormat(MIDI, format, len);
62     }
63 
getAudioInputStream(final Sequence seq)64     private AudioInputStream getAudioInputStream(final Sequence seq)
65             throws InvalidMidiDataException {
66         AudioSynthesizer synth = new SoftSynthesizer();
67         AudioInputStream stream;
68         Receiver recv;
69         try {
70             stream = synth.openStream(format, null);
71             recv = synth.getReceiver();
72         } catch (MidiUnavailableException e) {
73             throw new InvalidMidiDataException(e.toString());
74         }
75         float divtype = seq.getDivisionType();
76         Track[] tracks = seq.getTracks();
77         int[] trackspos = new int[tracks.length];
78         int mpq = 500000;
79         int seqres = seq.getResolution();
80         long lasttick = 0;
81         long curtime = 0;
82         while (true) {
83             MidiEvent selevent = null;
84             int seltrack = -1;
85             for (int i = 0; i < tracks.length; i++) {
86                 int trackpos = trackspos[i];
87                 Track track = tracks[i];
88                 if (trackpos < track.size()) {
89                     MidiEvent event = track.get(trackpos);
90                     if (selevent == null || event.getTick() < selevent.getTick()) {
91                         selevent = event;
92                         seltrack = i;
93                     }
94                 }
95             }
96             if (seltrack == -1)
97                 break;
98             trackspos[seltrack]++;
99             long tick = selevent.getTick();
100             if (divtype == Sequence.PPQ)
101                 curtime += ((tick - lasttick) * mpq) / seqres;
102             else
103                 curtime = (long) ((tick * 1000000.0 * divtype) / seqres);
104             lasttick = tick;
105             MidiMessage msg = selevent.getMessage();
106             if (msg instanceof MetaMessage) {
107                 if (divtype == Sequence.PPQ) {
108                     if (((MetaMessage) msg).getType() == 0x51) {
109                         byte[] data = ((MetaMessage) msg).getData();
110                         if (data.length < 3) {
111                             throw new InvalidMidiDataException();
112                         }
113                         mpq = ((data[0] & 0xff) << 16)
114                                 | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
115                     }
116                 }
117             } else {
118                 recv.send(msg, curtime);
119             }
120         }
121 
122         long totallen = curtime / 1000000;
123         long len = (long) (stream.getFormat().getFrameRate() * (totallen + 4));
124         stream = new AudioInputStream(stream, stream.getFormat(), len);
125         return stream;
126     }
127 
128     @Override
getAudioInputStream(final InputStream stream)129     public AudioInputStream getAudioInputStream(final InputStream stream)
130             throws UnsupportedAudioFileException, IOException {
131         stream.mark(200);
132         try {
133             return getAudioInputStream(MidiSystem.getSequence(stream));
134         } catch (InvalidMidiDataException | EOFException ignored) {
135             // stream is unsupported or the header is less than was expected
136             stream.reset();
137             throw new UnsupportedAudioFileException();
138         }
139     }
140 
141     @Override
getAudioFileFormatImpl(final InputStream stream)142     StandardFileFormat getAudioFileFormatImpl(final InputStream stream)
143             throws UnsupportedAudioFileException, IOException {
144         try {
145             return getAudioFileFormat(MidiSystem.getSequence(stream));
146         } catch (final InvalidMidiDataException ignored) {
147             throw new UnsupportedAudioFileException();
148         }
149     }
150 }
151