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 
  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 
  64     private AudioInputStream getAudioInputStream(final Sequence seq)
  65             throws InvalidMidiDataException {
  66         AudioSynthesizer synth = (AudioSynthesizer) 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
 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
 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 }