/*
* MidiPlayer.java
*
* This file is part of jsresources.org
*/
/*
* Copyright (c) 1999 - 2006 by Matthias Pfisterer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.sound.midi.ControllerEventListener;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;
/* If the compilation fails because this class is not available,
get gnu.getopt from the URL given in the comment below.
*/
import gnu.getopt.Getopt;
/** <titleabbrev>MidiPlayer</titleabbrev>
<title>Playing a MIDI file (advanced)</title>
<formalpara><title>Purpose</title>
<para>Plays a single MIDI file. Allows to select the sequencer,
the synthesizer or MIDI port or dumping to the console.</para>
</formalpara>
<formalpara><title>Usage</title>
<para>
<cmdsynopsis>
<command>java MidiPlayer</command>
<arg choice="plain"><option>-l</option></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>java MidiPlayer</command>
<arg><option>-s</option></arg>
<arg><option>-m</option></arg>
<arg><option>-d <replaceable>devicename</replaceable></option></arg>
<arg><option>-c</option></arg>
<arg><option>-S <replaceable>sequencername</replaceable></option></arg>
<arg choice="plain"><replaceable>midifile</replaceable></arg>
</cmdsynopsis>
</para></formalpara>
<formalpara><title>Parameters</title>
<variablelist>
<varlistentry>
<term><option>-l</option></term>
<listitem><para>list the availabe MIDI devices, including sequencers</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-m</option></term>
<listitem><para>play on the MIDI port</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-d <replaceable>devicename</replaceable></option></term>
<listitem><para>play on the named MIDI device</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-c</option></term>
<listitem><para>dump on the console</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-S <replaceable>sequencername</replaceable></option></term>
<listitem><para>play using the named Sequencer</para></listitem>
</varlistentry>
<varlistentry>
<term><option><replaceable>midifile</replaceable></option></term>
<listitem><para>the name of the MIDI file that should be
played</para></listitem>
</varlistentry>
</variablelist>
<para>All options may be used together.
No option is equal to giving <option>-s</option>.</para>
</formalpara>
<formalpara><title>Bugs, limitations</title>
<para>This example requires the JDK1.5 or later.
</para>
</formalpara>
<formalpara><title>Source code</title>
<para>
<ulink url="MidiPlayer.java.html">MidiPlayer.java</ulink>,
<ulink url="DumpReceiver.java.html">DumpReceiver.java</ulink>,
<ulink url="MidiCommon.java.html">MidiCommon.java</ulink>,
<ulink url="http://www.urbanophile.com/arenn/hacking/download.html">gnu.getopt.Getopt</ulink>
</para>
</formalpara>
*/
public class MidiPlayer
{
/** Flag for debugging messages.
If true, some messages are dumped to the console
during operation.
*/
private static boolean DEBUG = false;
private static Sequencer sm_sequencer = null;
/** List of opened MidiDevices.
This stores references to all MidiDevices that we've
opened except the sequencer.
It is used to close them properly on exit.
*/
private static List sm_openedMidiDeviceList;
private static boolean sm_bFinished = false;
public static void main(String[] args)
{
/*
* Set when the sequence should be played on the default
* internal synthesizer.
*/
boolean bUseSynthesizer = false;
/*
* Set when the sequence should be played on the default
* external MIDI port.
*/
boolean bUseMidiPort = false;
/*
* Set when the sequence should be played on a MidiDevice
* whose name is in strDeviceName. This can be any device,
* including internal or external synthesizers, MIDI ports
* or even sequencers.
*/
boolean bUseDevice = false;
/*
* Set when the sequence should be dumped in the console window
* (or whereever the standard output is routed to). This gives
* detailed information about each MIDI event.
*/
boolean bUseConsoleDump = false;
/*
* The device name to use when bUseDevice is set.
*/
String strDeviceName = null;
/*
* The name of the sequencer to use. This is optional. If not
* set, the default sequencer is used.
*/
String strSequencerName = null;
/*
* Parsing of command-line options takes place...
*/
Getopt g = new Getopt("MidiPlayer", args, "hlsmd:cS:D");
int c;
while ((c = g.getopt()) != -1)
{
switch (c)
{
case 'h':
printUsageAndExit();
case 'l':
MidiCommon.listDevicesAndExit(false, true);
case 's':
bUseSynthesizer = true;
break;
case 'm':
bUseMidiPort = true;
break;
case 'd':
bUseDevice = true;
strDeviceName = g.getOptarg();
if (DEBUG)
{
out("MidiPlayer.main(): device name: " + strDeviceName);
}
break;
case 'c':
bUseConsoleDump = true;
break;
case 'S':
strSequencerName = g.getOptarg();
if (DEBUG)
{
out("MidiPlayer.main(): sequencer name: " + strSequencerName);
}
break;
case 'D':
DEBUG = true;
break;
case '?':
printUsageAndExit();
default:
out("getopt() returned " + c);
break;
}
}
/*
* If no destination option is choosen at all,
* we default to playing on the internal synthesizer.
*/
if (!(bUseSynthesizer | bUseMidiPort | bUseDevice | bUseConsoleDump))
{
if (DEBUG) out("using default synthesizer because no other option was given");
bUseSynthesizer = true;
}
/*
* We make shure that there is only one more argument, which
* we take as the filename of the MIDI file we want to play.
*/
String strFilename = null;
for (int i = g.getOptind(); i < args.length; i++)
{
if (strFilename == null)
{
strFilename = args[i];
}
else
{
printUsageAndExit();
}
}
if (strFilename == null)
{
printUsageAndExit();
}
File midiFile = new File(strFilename);
/*
* We create a Sequence object from the input file. This is
* set later at the Sequencer as its sequence.
*
* We create an (File)InputStream and decorate it with a
* buffered stream. This is set later at the Sequencer as the
* source of a sequence.
* There is another programming technique: Creating an
* (File)InputStream and set this at the sequencer as the
* source of a sequence. While this technique seems somewhat
* unnatural, it in fact is more efficient on Sun's
* implementation of the Java Sound API up to version 1.4.2 of
* the JDK. Furthermore, it allows playback of RMF files.
*
* However, the technique used above should be considered the
* standard technique. It is especially appropriate if the JDK
* 1.5.0 or Tritonus is used.
*/
Sequence sequence = null;
try
{
if (DEBUG) out("before MIDI file reading.");
sequence = MidiSystem.getSequence(midiFile);
if (DEBUG) out("MIDI file read.");
}
catch (InvalidMidiDataException e)
{
printExceptionAndExit(e);
}
catch (IOException e)
{
printExceptionAndExit(e);
}
/*
* Now, we need a Sequencer to play the sequence.
* In case we have passed a sequencer name on the command line,
* we try to get that specific sequencer.
* Otherwise, we simply request the default sequencer.
*/
try
{
if (strSequencerName != null)
{
MidiDevice.Info seqInfo = MidiCommon.getMidiDeviceInfo(strSequencerName, true);
if (seqInfo == null)
{
out("Cannot find device " + strSequencerName);
System.exit(1);
}
sm_sequencer = (Sequencer) MidiSystem.getMidiDevice(seqInfo);
if (DEBUG) out("Sequencer: " + sm_sequencer);
}
else
{
/* We obtain an unconnected sequencer since we always connect
* synthesizers explicitly in the code below.
*/
sm_sequencer = MidiSystem.getSequencer(false);
}
}
catch (MidiUnavailableException e)
{
printExceptionAndExit(e);
}
if (sm_sequencer == null)
{
out("MidiPlayer.main(): can't get a Sequencer");
System.exit(1);
}
if (DEBUG) out("Sequencer: " + sm_sequencer);
/*
* There is a bug in the Sun jdk1.3/1.4.
* It prevents correct termination of the VM.
* So we have to exit ourselves.
* To accomplish this, we register a Listener to the Sequencer.
* It is called when there are "meta" events. Meta event
* 47 is end of track.
*
* Thanks to Espen Riskedal for finding this trick.
*/
sm_sequencer.addMetaEventListener(new MetaEventListener()
{
public void meta(MetaMessage event)
{
if (event.getType() == 47)
{
if (DEBUG) { out("MidiPlayer.<...>.meta(): end of track message received, closing sequencer and attached MidiDevices..."); }
sm_sequencer.close();
Iterator iterator = sm_openedMidiDeviceList.iterator();
while (iterator.hasNext())
{
MidiDevice device = (MidiDevice) iterator.next();
device.close();
}
if (DEBUG) { out("MidiPlayer.<...>.meta(): ...closed, now exiting"); }
sm_bFinished = true;
//System.exit(0);
}
}
});
/*
* If we are in debug mode, we set additional listeners
* to produce interesting (?) debugging output.
*/
if (DEBUG)
{
sm_sequencer.addMetaEventListener(
new MetaEventListener()
{
public void meta(MetaMessage message)
{
out("%%% MetaMessage: " + message);
out("%%% MetaMessage type: " + message.getType());
out("%%% MetaMessage length: " + message.getLength());
}
});
int[] anControllers = new int[128];
for (int i = 0; i < anControllers.length; i++)
{
anControllers[i] = i;
}
sm_sequencer.addControllerEventListener(
new ControllerEventListener()
{
public void controlChange(ShortMessage message)
{
out("%%% ShortMessage: " + message);
out("%%% ShortMessage controller: " + message.getData1());
out("%%% ShortMessage value: " + message.getData2());
}
},
anControllers);
}
/*
* The Sequencer is still a dead object.
* We have to open() it to become live.
* This is necessary to allocate some ressources in
* the native part.
*/
try
{
sm_sequencer.open();
}
catch (MidiUnavailableException e)
{
printExceptionAndExit(e);
}
if (DEBUG) out("Sequencer opened.");
/*
* Next step is to tell the Sequencer which
* Sequence it has to play. In this case, we
* set it as the InputStream created above.
*/
try
{
sm_sequencer.setSequence(sequence);
}
catch (InvalidMidiDataException e)
{
printExceptionAndExit(e);
}
if (DEBUG) out("Sequence set.");
/*
* Now, we set up the destinations the Sequence should be
* played on.
*/
sm_openedMidiDeviceList = new ArrayList();
if (bUseSynthesizer)
{
/* For the Sun implementation of Java Sound (up to 1.4.2),
the default Sequencer is also a
Synthesizer. So to play only on the default
Synthesizer, no further actions are
required. However, this is
implementation-specific behaviour. To write
portable programs, it is strongly recommanded
to follow the programming technique shown
below.
*/
if (sm_sequencer instanceof Synthesizer)
{
/* Sun implementation; no action required. */
}
else
{
/*
* We try to get the default synthesizer, open()
* it and chain it to the sequencer with a
* Transmitter-Receiver pair.
*/
try
{
Synthesizer synth = MidiSystem.getSynthesizer();
synth.open();
sm_openedMidiDeviceList.add(synth);
Receiver synthReceiver = synth.getReceiver();
Transmitter seqTransmitter = sm_sequencer.getTransmitter();
seqTransmitter.setReceiver(synthReceiver);
}
catch (MidiUnavailableException e)
{
e.printStackTrace();
}
}
}
if (bUseMidiPort)
{
/*
* We try to get a Receiver which is already
* associated with the default MIDI port.
* It is then linked to a sequencer's
* Transmitter.
*/
try
{
Receiver midiReceiver = MidiSystem.getReceiver();
Transmitter midiTransmitter = sm_sequencer.getTransmitter();
midiTransmitter.setReceiver(midiReceiver);
}
catch (MidiUnavailableException e)
{
e.printStackTrace();
}
}
if (bUseDevice)
{
/* Here, we try to use a MidiDevice as destination
* whose name was passed on the command line.
* It is then linked to a sequencer's
* Transmitter.
*/
MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
MidiDevice.Info info = MidiCommon.getMidiDeviceInfo(strDeviceName, true);
if (info == null)
{
out("Cannot find device " + strDeviceName);
}
try
{
MidiDevice midiDevice = MidiSystem.getMidiDevice(info);
midiDevice.open();
sm_openedMidiDeviceList.add(midiDevice);
Receiver midiReceiver = midiDevice.getReceiver();
Transmitter midiTransmitter = sm_sequencer.getTransmitter();
midiTransmitter.setReceiver(midiReceiver);
}
catch (MidiUnavailableException e)
{
e.printStackTrace();
}
}
if (bUseConsoleDump)
{
/*
* We allocate a DumpReceiver object. Its job
* is to print information on all received events
* to the console.
* It is then linked to a sequencer's
* Transmitter.
*/
try
{
Receiver dumpReceiver = new DumpReceiver(System.out);
Transmitter dumpTransmitter = sm_sequencer.getTransmitter();
dumpTransmitter.setReceiver(dumpReceiver);
}
catch (MidiUnavailableException e)
{
e.printStackTrace();
}
}
/*
* Now, we can start over.
*/
if (DEBUG) { out("MidiPlayer.main(): starting sequencer..."); }
sm_sequencer.start();
if (DEBUG) { out("MidiPlayer.main(): ...started"); }
while (! sm_bFinished)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
// IGNORE
}
}
// TODO: close devices
}
private static void printUsageAndExit()
{
out("MidiPlayer: usage:");
out(" java MidiPlayer -h");
out(" gives help information");
out(" java MidiPlayer -l");
out(" lists available MIDI devices");
out(" java MidiPlayer [-s] [-m] [-d <output device name>] [-c] [-S <sequencer name>] [-D] <midifile>");
out(" -s\tplays on the default synthesizer");
out(" -m\tplays on the MIDI port");
out(" -d <output device name>\toutputs to named device (see '-l')");
out(" -c\tdumps to the console");
out(" -S <sequencer name>\tuses named sequencer (see '-l')");
out(" -D\tenables debugging output");
out("All options may be used together.");
out("No option is equal to giving -s.");
System.exit(1);
}
/** Handle Exception.
* In case of an exception, we dump the exception
* including the stack trace to the console
* output. Then, we exit the program.
*/
private static void printExceptionAndExit(Exception e)
{
e.printStackTrace();
System.exit(1);
}
private static void listDevicesAndExit(boolean forInput, boolean forOutput)
{
if (forInput && !forOutput) {
out("Available MIDI IN Devices:");
}
else if (!forInput && forOutput) {
out("Available MIDI OUT Devices:");
} else {
out("Available MIDI Devices:");
}
MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
for (int i = 0; i < aInfos.length; i++) {
try {
MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
boolean bAllowsInput = (device.getMaxTransmitters() != 0);
boolean bAllowsOutput = (device.getMaxReceivers() != 0);
if ((bAllowsInput && forInput) || (bAllowsOutput && forOutput)) {
out(""+i+" "
+(bAllowsInput?"IN ":" ")
+(bAllowsOutput?"OUT ":" ")
+aInfos[i].getName()+", "
+aInfos[i].getVendor()+", "
+aInfos[i].getVersion()+", "
+aInfos[i].getDescription());
}
}
catch (MidiUnavailableException e) {
// device is obviously not available...
}
}
if (aInfos.length == 0) {
out("[No devices available]");
}
System.exit(0);
}
/*
* This method tries to return a MidiDevice.Info whose name
* matches the passed name. If no matching MidiDevice.Info is
* found, null is returned.
* If forOutput is true, then only output devices are searched,
* otherwise only input devices.
*/
// TODO: check against MidiCommon
private static MidiDevice.Info getMidiDeviceInfo(String strDeviceName,
boolean forOutput)
{
MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
for (int i = 0; i < aInfos.length; i++) {
if (aInfos[i].getName().equals(strDeviceName)) {
try {
MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
boolean bAllowsInput = (device.getMaxTransmitters() != 0);
boolean bAllowsOutput = (device.getMaxReceivers() != 0);
if ((bAllowsOutput && forOutput) || (bAllowsInput && !forOutput)) {
return aInfos[i];
}
} catch (MidiUnavailableException mue) {}
}
}
return null;
}
private static void out(String strMessage)
{
System.out.println(strMessage);
}
}
/*** MidiPlayer.java ***/