/* * MidiRecorder.java * * This file is part of jsresources.org */ /* * Copyright (c) 1999 - 2001 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.BufferedInputStream; import java.io.File; import java.io.FileInputStream; 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.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>MidiRecorder</titleabbrev> <title>Plays a single MIDI or RMF file</title> <formalpara><title>Purpose</title> <para>Plays a single MIDI or RMF file.</para></formalpara> <formalpara><title>Usage</title> <para> <synopsis>java MidiRecorder -l</synopsis> <synopsis>java MidiRecorder [-s] [-m] [-d <devicename>] [-c] [-S <sequencername>] <midifile></synopsis> </para></formalpara> <formalpara><title>Parameters</title> <variablelist> <varlistentry> <term><option>-l</option></term> <listitem><para>list the availabe MIDI devices</para></listitem> </varlistentry> <varlistentry> <term><option>-m</option></term> <listitem><para>play on the MIDI port</para></listitem> </varlistentry> <varlistentry> <term><option>-d <devicename></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> All options may be used together. No option is equal to giving <option>-s</option>. </varlistentry> <varlistentry> <term><option>-S <sequencername></option></term> <listitem><para>play using the named Sequencer</para></listitem> </varlistentry> <varlistentry> <term><option><midifile></option></term> <listitem><para>the file name of the MIDI or RMF file that should be played</para></listitem> </varlistentry> </variablelist> </formalpara> <formalpara><title>Bugs, limitations</title> <para> For the Sun jdk1.3/1.4, playing to the MIDI port and dumping to the console do not work. You can make it work by installing the <ulink url ="http://www.tritonus.org/plugins.html">MidiShare plug-in</ulink>. For Tritonus, playing RMF files does not work (and will not work until the specs are published). </para></formalpara> <formalpara><title>Source code</title> <para> <ulink url="MidiRecorder.java.html">MidiRecorder.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 MidiRecorder { /** 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; 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("MidiRecorder", args, "hlsmd:cS:D"); int c; while ((c = g.getopt()) != -1) { switch (c) { case 'h': printUsageAndExit(); case 'l': MidiCommon.listDevicesAndExit(true, false); case 's': bUseSynthesizer = true; break; case 'm': bUseMidiPort = true; break; case 'd': bUseDevice = true; strDeviceName = g.getOptarg(); if (DEBUG) { out("MidiRecorder.main(): device name: " + strDeviceName); } break; case 'c': bUseConsoleDump = true; break; case 'S': strSequencerName = g.getOptarg(); if (DEBUG) { out("MidiRecorder.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)) { 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 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 a * Sequence object from the file and set this at the * Sequencer. While this technique seems more natural, * it in fact is less efficient on Sun's implementation * of the Java Sound API. Furthermore, it sucks for RMF * files. So for now, I consider the technique used * here as the "official" one. * But note that this depends * on facts that are implementation-dependant; it is * only true for the Sun implementation. In Tritonus, * efficiency is the other way round. * (And Tritonus has no RMF support because the * specs are proprietary.) */ InputStream sequenceStream = null; try { sequenceStream = new FileInputStream(midiFile); sequenceStream = new BufferedInputStream(sequenceStream, 1024); } catch (IOException e) { /* * In case of an exception, we dump the exception * including the stack trace to the console * output. Then, we exit the program. */ e.printStackTrace(); System.exit(1); } /* * 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, false); if (seqInfo == null) { out("Cannot find device " + strSequencerName); System.exit(1); } sm_sequencer = (Sequencer) MidiSystem.getMidiDevice(seqInfo); } else { sm_sequencer = MidiSystem.getSequencer(); } } catch (MidiUnavailableException e) { e.printStackTrace(); System.exit(1); } if (sm_sequencer == null) { out("MidiRecorder.main(): can't get a Sequencer"); System.exit(1); } /* * 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("MidiRecorder.<...>.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("MidiRecorder.<...>.meta(): ...closed, now exiting"); } 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()); } }); sm_sequencer.addControllerEventListener( new ControllerEventListener() { public void controlChange(ShortMessage message) { out("%%% ShortMessage: " + message); out("%%% ShortMessage controller: " + message.getData1()); out("%%% ShortMessage value: " + message.getData2()); } }, null); } /* * 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) { e.printStackTrace(); System.exit(1); } /* * 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(sequenceStream); } catch (InvalidMidiDataException e) { e.printStackTrace(); System.exit(1); } catch (IOException e) { e.printStackTrace(); System.exit(1); } /* * Now, we set up the destinations the Sequence should be * played on. */ sm_openedMidiDeviceList = new ArrayList(); if (bUseSynthesizer) { /* * 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, false); 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("MidiRecorder.main(): starting sequencer..."); } sm_sequencer.start(); if (DEBUG) { out("MidiRecorder.main(): ...started"); } } private static void printUsageAndExit() { out("MidiRecorder: usage:"); out(" java MidiRecorder -h"); out(" gives help information"); out(" java MidiRecorder -l"); out(" lists available MIDI devices"); out(" java MidiRecorder [-s] [-m] [-d <output device name>] [-c] [-S <sequencer name>] [-D] <midifile>"); out(" -s\tplays on the internal 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); } private static void out(String strMessage) { System.out.println(strMessage); } } /*** MidiRecorder.java ***/