1 /*
   2  * Copyright (c) 2007, 2014, 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.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.net.URL;
  32 
  33 import javax.sound.midi.InvalidMidiDataException;
  34 import javax.sound.midi.MetaMessage;
  35 import javax.sound.midi.MidiEvent;
  36 import javax.sound.midi.MidiMessage;
  37 import javax.sound.midi.MidiSystem;
  38 import javax.sound.midi.MidiUnavailableException;
  39 import javax.sound.midi.Receiver;
  40 import javax.sound.midi.Sequence;
  41 import javax.sound.midi.Track;
  42 import javax.sound.sampled.AudioFileFormat;
  43 import javax.sound.sampled.AudioFileFormat.Type;
  44 import javax.sound.sampled.AudioFormat;
  45 import javax.sound.sampled.AudioInputStream;
  46 import javax.sound.sampled.UnsupportedAudioFileException;
  47 import javax.sound.sampled.spi.AudioFileReader;
  48 
  49 /**
  50  * MIDI File Audio Renderer/Reader.
  51  *
  52  * @author Karl Helgason
  53  */
  54 public final class SoftMidiAudioFileReader extends AudioFileReader {
  55 
  56     public static final Type MIDI = new Type("MIDI", "mid");
  57     private static AudioFormat format = new AudioFormat(44100, 16, 2, true, false);
  58 
  59     public AudioFileFormat getAudioFileFormat(Sequence seq)
  60             throws UnsupportedAudioFileException, IOException {
  61 
  62         long totallen = seq.getMicrosecondLength() / 1000000;
  63         long len = (long) (format.getFrameRate() * (totallen + 4));
  64         return new AudioFileFormat(MIDI, format, (int) len);
  65     }
  66 
  67     public AudioInputStream getAudioInputStream(Sequence seq)
  68             throws UnsupportedAudioFileException, IOException {
  69         AudioSynthesizer synth = (AudioSynthesizer) new SoftSynthesizer();
  70         AudioInputStream stream;
  71         Receiver recv;
  72         try {
  73             stream = synth.openStream(format, null);
  74             recv = synth.getReceiver();
  75         } catch (MidiUnavailableException e) {
  76             throw new IOException(e.toString());
  77         }
  78         float divtype = seq.getDivisionType();
  79         Track[] tracks = seq.getTracks();
  80         int[] trackspos = new int[tracks.length];
  81         int mpq = 500000;
  82         int seqres = seq.getResolution();
  83         long lasttick = 0;
  84         long curtime = 0;
  85         while (true) {
  86             MidiEvent selevent = null;
  87             int seltrack = -1;
  88             for (int i = 0; i < tracks.length; i++) {
  89                 int trackpos = trackspos[i];
  90                 Track track = tracks[i];
  91                 if (trackpos < track.size()) {
  92                     MidiEvent event = track.get(trackpos);
  93                     if (selevent == null || event.getTick() < selevent.getTick()) {
  94                         selevent = event;
  95                         seltrack = i;
  96                     }
  97                 }
  98             }
  99             if (seltrack == -1)
 100                 break;
 101             trackspos[seltrack]++;
 102             long tick = selevent.getTick();
 103             if (divtype == Sequence.PPQ)
 104                 curtime += ((tick - lasttick) * mpq) / seqres;
 105             else
 106                 curtime = (long) ((tick * 1000000.0 * divtype) / seqres);
 107             lasttick = tick;
 108             MidiMessage msg = selevent.getMessage();
 109             if (msg instanceof MetaMessage) {
 110                 if (divtype == Sequence.PPQ) {
 111                     if (((MetaMessage) msg).getType() == 0x51) {
 112                         byte[] data = ((MetaMessage) msg).getData();
 113                         if (data.length < 3) {
 114                             throw new UnsupportedAudioFileException();
 115                         }
 116                         mpq = ((data[0] & 0xff) << 16)
 117                                 | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
 118                     }
 119                 }
 120             } else {
 121                 recv.send(msg, curtime);
 122             }
 123         }
 124 
 125         long totallen = curtime / 1000000;
 126         long len = (long) (stream.getFormat().getFrameRate() * (totallen + 4));
 127         stream = new AudioInputStream(stream, stream.getFormat(), len);
 128         return stream;
 129     }
 130 
 131     public AudioInputStream getAudioInputStream(InputStream inputstream)
 132             throws UnsupportedAudioFileException, IOException {
 133 
 134         inputstream.mark(200);
 135         Sequence seq;
 136         try {
 137             seq = MidiSystem.getSequence(inputstream);
 138         } catch (InvalidMidiDataException e) {
 139             inputstream.reset();
 140             throw new UnsupportedAudioFileException();
 141         } catch (IOException e) {
 142             inputstream.reset();
 143             throw new UnsupportedAudioFileException();
 144         }
 145         return getAudioInputStream(seq);
 146     }
 147 
 148     public AudioFileFormat getAudioFileFormat(URL url)
 149             throws UnsupportedAudioFileException, IOException {
 150         Sequence seq;
 151         try {
 152             seq = MidiSystem.getSequence(url);
 153         } catch (InvalidMidiDataException e) {
 154             throw new UnsupportedAudioFileException();
 155         } catch (IOException e) {
 156             throw new UnsupportedAudioFileException();
 157         }
 158         return getAudioFileFormat(seq);
 159     }
 160 
 161     public AudioFileFormat getAudioFileFormat(File file)
 162             throws UnsupportedAudioFileException, IOException {
 163         Sequence seq;
 164         try {
 165             seq = MidiSystem.getSequence(file);
 166         } catch (InvalidMidiDataException e) {
 167             throw new UnsupportedAudioFileException();
 168         } catch (IOException e) {
 169             throw new UnsupportedAudioFileException();
 170         }
 171         return getAudioFileFormat(seq);
 172     }
 173 
 174     public AudioInputStream getAudioInputStream(URL url)
 175             throws UnsupportedAudioFileException, IOException {
 176         Sequence seq;
 177         try {
 178             seq = MidiSystem.getSequence(url);
 179         } catch (InvalidMidiDataException e) {
 180             throw new UnsupportedAudioFileException();
 181         } catch (IOException e) {
 182             throw new UnsupportedAudioFileException();
 183         }
 184         return getAudioInputStream(seq);
 185     }
 186 
 187     public AudioInputStream getAudioInputStream(File file)
 188             throws UnsupportedAudioFileException, IOException {
 189         if (!file.getName().toLowerCase().endsWith(".mid"))
 190             throw new UnsupportedAudioFileException();
 191         Sequence seq;
 192         try {
 193             seq = MidiSystem.getSequence(file);
 194         } catch (InvalidMidiDataException e) {
 195             throw new UnsupportedAudioFileException();
 196         } catch (IOException e) {
 197             throw new UnsupportedAudioFileException();
 198         }
 199         return getAudioInputStream(seq);
 200     }
 201 
 202     public AudioFileFormat getAudioFileFormat(InputStream inputstream)
 203             throws UnsupportedAudioFileException, IOException {
 204 
 205         inputstream.mark(200);
 206         Sequence seq;
 207         try {
 208             seq = MidiSystem.getSequence(inputstream);
 209         } catch (InvalidMidiDataException e) {
 210             inputstream.reset();
 211             throw new UnsupportedAudioFileException();
 212         } catch (IOException e) {
 213             inputstream.reset();
 214             throw new UnsupportedAudioFileException();
 215         }
 216         return getAudioFileFormat(seq);
 217     }
 218 }