/* * 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 ***/