code: /*
   * Computoser is a music-composition algorithm and a website to present the results
   * Copyright (C) 2012-2014  Bozhidar Bozhanov
   *
   * Computoser is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * Computoser is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with Computoser.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package com.music;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  
  import javax.sound.midi.InvalidMidiDataException;
  import javax.sound.midi.MetaMessage;
  import javax.sound.midi.MidiDevice;
  import javax.sound.midi.MidiDevice.Info;
  import javax.sound.midi.MidiEvent;
  import javax.sound.midi.MidiMessage;
  import javax.sound.midi.MidiSystem;
  import javax.sound.midi.MidiUnavailableException;
  import javax.sound.midi.Receiver;
  import javax.sound.midi.Sequence;
  import javax.sound.midi.Synthesizer;
  import javax.sound.midi.Track;
  import javax.sound.sampled.AudioFileFormat;
  import javax.sound.sampled.AudioInputStream;
  import javax.sound.sampled.AudioSystem;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.sun.media.sound.AudioSynthesizer;
  
  public class Midi2WavRenderer {
      private static final Logger logger = LoggerFactory.getLogger(Midi2WavRenderer.class);
  
      //TODO pool synthesizers
  
      public static void midi2wav(InputStream is, OutputStream os) {
          try {
              Sequence sequence = MidiSystem.getSequence(is);
              render(sequence, os);
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
      }
  
      /*
       * Render sequence using selected or default soundbank into wave audio file.
       */
      public static void render(Sequence sequence, OutputStream outStream) {
          try {
              // Find available AudioSynthesizer.
              AudioSynthesizer synth = findAudioSynthesizer();
              if (synth == null) {
                  logger.warn("No AudioSynhtesizer was found!");
                  return;
              }
  
              // Open AudioStream from AudioSynthesizer.
              AudioInputStream stream = synth.openStream(null, null);
  
              Generator.loadSoundbankInstruments(synth);
              // Play Sequence into AudioSynthesizer Receiver.
              double total = send(sequence, synth.getReceiver());
  
              // Calculate how long the WAVE file needs to be.
              long len = (long) (stream.getFormat().getFrameRate() * (total + 4));
              stream = new AudioInputStream(stream, stream.getFormat(), len);
  
              AudioSystem.write(stream, AudioFileFormat.Type.WAVE, outStream);
  
              synth.close();
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      /*
       * Find available AudioSynthesizer.
       */
      public static AudioSynthesizer findAudioSynthesizer() throws MidiUnavailableException, IOException, InvalidMidiDataException {
          // First check if default synthesizer is AudioSynthesizer.
          Synthesizer synth = MidiSystem.getSynthesizer();
          if (synth instanceof AudioSynthesizer) {
              return (AudioSynthesizer) synth;
          }
  
          // If default synhtesizer is not AudioSynthesizer, check others.
          Info[] infos = MidiSystem.getMidiDeviceInfo();
          for (int i = 0; i < infos.length; i++) {
              MidiDevice dev = MidiSystem.getMidiDevice(infos[i]);
              if (dev instanceof AudioSynthesizer) {
                  return (AudioSynthesizer) dev;
              }
          }
  
          // No AudioSynthesizer was found, return null.
          return null;
      }
  
      /*
       * Send entiry MIDI Sequence into Receiver using timestamps.
       */
      public static double send(Sequence seq, Receiver recv) {
          float divtype = seq.getDivisionType();
          assert (seq.getDivisionType() == Sequence.PPQ);
          Track[] tracks = seq.getTracks();
          int[] trackspos = new int[tracks.length];
          int mpq = 500000;
          int seqres = seq.getResolution();
          long lasttick = 0;
          long curtime = 0;
          while (true) {
              MidiEvent selevent = null;
              int seltrack = -1;
              for (int i = 0; i < tracks.length; i++) {
                  int trackpos = trackspos[i];
                  Track track = tracks[i];
                  if (trackpos < track.size()) {
                      MidiEvent event = track.get(trackpos);
                      if (selevent == null || event.getTick() < selevent.getTick()) {
                          selevent = event;
                          seltrack = i;
                      }
                  }
              }
              if (seltrack == -1)
                  break;
              trackspos[seltrack]++;
              long tick = selevent.getTick();
              if (divtype == Sequence.PPQ)
                  curtime += ((tick - lasttick) * mpq) / seqres;
              else
                  curtime = (long) ((tick * 1000000.0 * divtype) / seqres);
              lasttick = tick;
              MidiMessage msg = selevent.getMessage();
              if (msg instanceof MetaMessage) {
                  if (divtype == Sequence.PPQ)
                      if (((MetaMessage) msg).getType() == 0x51) {
                          byte[] data = ((MetaMessage) msg).getData();
                          mpq = ((data[0] & 0xff) << 16) | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
                      }
              } else {
                  if (recv != null)
                      recv.send(msg, curtime);
              }
          }
  
          return curtime / 1000000.0;
      }
  
  }
  