/* * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.sound.midi; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Properties; import java.util.Set; import javax.sound.midi.spi.MidiDeviceProvider; import javax.sound.midi.spi.MidiFileReader; import javax.sound.midi.spi.MidiFileWriter; import javax.sound.midi.spi.SoundbankReader; import com.sun.media.sound.AutoConnectSequencer; import com.sun.media.sound.JDK13Services; import com.sun.media.sound.MidiDeviceReceiverEnvelope; import com.sun.media.sound.MidiDeviceTransmitterEnvelope; import com.sun.media.sound.ReferenceCountingDevice; /** * The {@code MidiSystem} class provides access to the installed MIDI system * resources, including devices such as synthesizers, sequencers, and MIDI input * and output ports. A typical simple MIDI application might begin by invoking * one or more {@code MidiSystem} methods to learn what devices are installed * and to obtain the ones needed in that application. *
* The class also has methods for reading files, streams, and URLs that contain * standard MIDI file data or soundbanks. You can query the {@code MidiSystem} * for the format of a specified MIDI file. *
* You cannot instantiate a {@code MidiSystem}; all the methods are static. *
* Properties can be used to specify default MIDI devices. Both system * properties and a properties file are considered. The "sound.properties" * properties file is read from an implementation-specific location (typically * it is the {@code conf} directory in the Java installation directory). If a * property exists both as a system property and in the properties file, the * system property takes precedence. If none is specified, a suitable default is * chosen among the available devices. The syntax of the properties file is * specified in {@link Properties#load(InputStream) Properties.load}. The * following table lists the available property keys and which methods consider * them: * *
Property Key | *Interface | *Affected Method | *
---|---|---|
{@code javax.sound.midi.Receiver} | *{@link Receiver} | *{@link #getReceiver} | *
{@code javax.sound.midi.Sequencer} | *{@link Sequencer} | *{@link #getSequencer} | *
{@code javax.sound.midi.Synthesizer} | *{@link Synthesizer} | *{@link #getSynthesizer} | *
{@code javax.sound.midi.Transmitter} | *{@link Transmitter} | *{@link #getTransmitter} | *
* If the provider class is specified, and it can be successfully retrieved from * the installed providers, the list of {@code MidiDevice.Info} objects is * retrieved from the provider. Otherwise, or when these devices do not provide * a subsequent match, the list is retrieved from {@link #getMidiDeviceInfo} to * contain all available {@code MidiDevice.Info} objects. *
* If a device name is specified, the resulting list of {@code MidiDevice.Info} * objects is searched: the first one with a matching name, and whose * {@code MidiDevice} implements the respective interface, will be returned. If * no matching {@code MidiDevice.Info} object is found, or the device name is * not specified, the first suitable device from the resulting list will be * returned. For Sequencer and Synthesizer, a device is suitable if it * implements the respective interface; whereas for Receiver and Transmitter, a * device is suitable if it implements neither Sequencer nor Synthesizer and * provides at least one Receiver or Transmitter, respectively. *
* For example, the property {@code javax.sound.midi.Receiver} with a value
* {@code "com.sun.media.sound.MidiProvider#SunMIDI1"} will have the following
* consequences when {@code getReceiver} is called: if the class
* {@code com.sun.media.sound.MidiProvider} exists in the list of installed MIDI
* device providers, the first {@code Receiver} device with name
* {@code "SunMIDI1"} will be returned. If it cannot be found, the first
* {@code Receiver} from that provider will be returned, regardless of name. If
* there is none, the first {@code Receiver} with name {@code "SunMIDI1"} in the
* list of all devices (as returned by {@code getMidiDeviceInfo}) will be
* returned, or, if not found, the first {@code Receiver} that can be found in
* the list of all devices is returned. If that fails, too, a
* {@code MidiUnavailableException} is thrown.
*
* @author Kara Kytle
* @author Florian Bomers
* @author Matthias Pfisterer
*/
public class MidiSystem {
/**
* Private no-args constructor for ensuring against instantiation.
*/
private MidiSystem() {
}
/**
* Obtains an array of information objects representing the set of all MIDI
* devices available on the system. A returned information object can then
* be used to obtain the corresponding device object, by invoking
* {@link #getMidiDevice(MidiDevice.Info) getMidiDevice}.
*
* @return an array of {@code MidiDevice.Info} objects, one for each
* installed MIDI device. If no such devices are installed, an array
* of length 0 is returned.
*/
public static MidiDevice.Info[] getMidiDeviceInfo() {
final List
* If the system property {@code javax.sound.midi.Receiver} is defined or it
* is defined in the file "sound.properties", it is used to identify the
* device that provides the default receiver. For details, refer to the
* {@link MidiSystem class description}.
*
* If a suitable MIDI port is not available, the Receiver is retrieved from
* an installed synthesizer.
*
* If a native receiver provided by the default device does not implement
* the {@code MidiDeviceReceiver} interface, it will be wrapped in a wrapper
* class that implements the {@code MidiDeviceReceiver} interface. The
* corresponding {@code Receiver} method calls will be forwarded to the
* native receiver.
*
* If this method returns successfully, the {@link MidiDevice MidiDevice}
* the {@code Receiver} belongs to is opened implicitly, if it is not
* already open. It is possible to close an implicitly opened device by
* calling {@link Receiver#close close} on the returned {@code Receiver}.
* All open {@code Receiver} instances have to be closed in order to release
* system resources hold by the {@code MidiDevice}. For a detailed
* description of open/close behaviour see the class description of
* {@link MidiDevice MidiDevice}.
*
* @return the default MIDI receiver
* @throws MidiUnavailableException if the default receiver is not available
* due to resource restrictions, or no device providing receivers is
* installed in the system
*/
public static Receiver getReceiver() throws MidiUnavailableException {
// may throw MidiUnavailableException
MidiDevice device = getDefaultDeviceWrapper(Receiver.class);
Receiver receiver;
if (device instanceof ReferenceCountingDevice) {
receiver = ((ReferenceCountingDevice) device).getReceiverReferenceCounting();
} else {
receiver = device.getReceiver();
}
if (!(receiver instanceof MidiDeviceReceiver)) {
receiver = new MidiDeviceReceiverEnvelope(device, receiver);
}
return receiver;
}
/**
* Obtains a MIDI transmitter from an external MIDI port or other default
* source. The returned transmitter always implements the
* {@code MidiDeviceTransmitter} interface.
*
* If the system property {@code javax.sound.midi.Transmitter} is defined or
* it is defined in the file "sound.properties", it is used to identify the
* device that provides the default transmitter. For details, refer to the
* {@link MidiSystem class description}.
*
* If a native transmitter provided by the default device does not implement
* the {@code MidiDeviceTransmitter} interface, it will be wrapped in a
* wrapper class that implements the {@code MidiDeviceTransmitter}
* interface. The corresponding {@code Transmitter} method calls will be
* forwarded to the native transmitter.
*
* If this method returns successfully, the {@link MidiDevice MidiDevice}
* the {@code Transmitter} belongs to is opened implicitly, if it is not
* already open. It is possible to close an implicitly opened device by
* calling {@link Transmitter#close close} on the returned
* {@code Transmitter}. All open {@code Transmitter} instances have to be
* closed in order to release system resources hold by the
* {@code MidiDevice}. For a detailed description of open/close behaviour
* see the class description of {@link MidiDevice MidiDevice}.
*
* @return the default MIDI transmitter
* @throws MidiUnavailableException if the default transmitter is not
* available due to resource restrictions, or no device providing
* transmitters is installed in the system
*/
public static Transmitter getTransmitter() throws MidiUnavailableException {
// may throw MidiUnavailableException
MidiDevice device = getDefaultDeviceWrapper(Transmitter.class);
Transmitter transmitter;
if (device instanceof ReferenceCountingDevice) {
transmitter = ((ReferenceCountingDevice) device).getTransmitterReferenceCounting();
} else {
transmitter = device.getTransmitter();
}
if (!(transmitter instanceof MidiDeviceTransmitter)) {
transmitter = new MidiDeviceTransmitterEnvelope(device, transmitter);
}
return transmitter;
}
/**
* Obtains the default synthesizer.
*
* If the system property {@code javax.sound.midi.Synthesizer} is defined or
* it is defined in the file "sound.properties", it is used to identify the
* default synthesizer. For details, refer to the
* {@link MidiSystem class description}.
*
* @return the default synthesizer
* @throws MidiUnavailableException if the synthesizer is not available due
* to resource restrictions, or no synthesizer is installed in the
* system
*/
public static Synthesizer getSynthesizer() throws MidiUnavailableException {
// may throw MidiUnavailableException
return (Synthesizer) getDefaultDeviceWrapper(Synthesizer.class);
}
/**
* Obtains the default {@code Sequencer}, connected to a default device. The
* returned {@code Sequencer} instance is connected to the default
* {@code Synthesizer}, as returned by {@link #getSynthesizer}. If there is
* no {@code Synthesizer} available, or the default {@code Synthesizer}
* cannot be opened, the {@code sequencer} is connected to the default
* {@code Receiver}, as returned by {@link #getReceiver}. The connection is
* made by retrieving a {@code Transmitter} instance from the
* {@code Sequencer} and setting its {@code Receiver}. Closing and
* re-opening the sequencer will restore the connection to the default
* device.
*
* This method is equivalent to calling {@code getSequencer(true)}.
*
* If the system property {@code javax.sound.midi.Sequencer} is defined or
* it is defined in the file "sound.properties", it is used to identify the
* default sequencer. For details, refer to the
* {@link MidiSystem class description}.
*
* @return the default sequencer, connected to a default Receiver
* @throws MidiUnavailableException if the sequencer is not available due to
* resource restrictions, or there is no {@code Receiver} available
* by any installed {@code MidiDevice}, or no sequencer is installed
* in the system
* @see #getSequencer(boolean)
* @see #getSynthesizer
* @see #getReceiver
*/
public static Sequencer getSequencer() throws MidiUnavailableException {
return getSequencer(true);
}
/**
* Obtains the default {@code Sequencer}, optionally connected to a default
* device.
*
* If {@code connected} is true, the returned {@code Sequencer} instance is
* connected to the default {@code Synthesizer}, as returned by
* {@link #getSynthesizer}. If there is no {@code Synthesizer} available, or
* the default {@code Synthesizer} cannot be opened, the {@code sequencer}
* is connected to the default {@code Receiver}, as returned by
* {@link #getReceiver}. The connection is made by retrieving a
* {@code Transmitter} instance from the {@code Sequencer} and setting its
* {@code Receiver}. Closing and re-opening the sequencer will restore the
* connection to the default device.
*
* If {@code connected} is false, the returned {@code Sequencer} instance is
* not connected, it has no open {@code Transmitters}. In order to play the
* sequencer on a MIDI device, or a {@code Synthesizer}, it is necessary to
* get a {@code Transmitter} and set its {@code Receiver}.
*
* If the system property {@code javax.sound.midi.Sequencer} is defined or
* it is defined in the file "sound.properties", it is used to identify the
* default sequencer. For details, refer to the
* {@link MidiSystem class description}.
*
* @param connected whether or not the returned {@code Sequencer} is
* connected to the default {@code Synthesizer}
* @return the default sequencer
* @throws MidiUnavailableException if the sequencer is not available due to
* resource restrictions, or no sequencer is installed in the
* system, or if {@code connected} is true, and there is no
* {@code Receiver} available by any installed {@code MidiDevice}
* @see #getSynthesizer
* @see #getReceiver
* @since 1.5
*/
public static Sequencer getSequencer(boolean connected)
throws MidiUnavailableException {
Sequencer seq = (Sequencer) getDefaultDeviceWrapper(Sequencer.class);
if (connected) {
// IMPORTANT: this code needs to be synch'ed with
// all AutoConnectSequencer instances,
// (e.g. RealTimeSequencer) because the
// same algorithm for synth retrieval
// needs to be used!
Receiver rec = null;
MidiUnavailableException mue = null;
// first try to connect to the default synthesizer
try {
Synthesizer synth = getSynthesizer();
if (synth instanceof ReferenceCountingDevice) {
rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
} else {
synth.open();
try {
rec = synth.getReceiver();
} finally {
// make sure that the synth is properly closed
if (rec == null) {
synth.close();
}
}
}
} catch (MidiUnavailableException e) {
// something went wrong with synth
if (e instanceof MidiUnavailableException) {
mue = e;
}
}
if (rec == null) {
// then try to connect to the default Receiver
try {
rec = MidiSystem.getReceiver();
} catch (Exception e) {
// something went wrong. Nothing to do then!
if (e instanceof MidiUnavailableException) {
mue = (MidiUnavailableException) e;
}
}
}
if (rec != null) {
seq.getTransmitter().setReceiver(rec);
if (seq instanceof AutoConnectSequencer) {
((AutoConnectSequencer) seq).setAutoConnect(rec);
}
} else {
if (mue != null) {
throw mue;
}
throw new MidiUnavailableException("no receiver available");
}
}
return seq;
}
/**
* Constructs a MIDI sound bank by reading it from the specified stream. The
* stream must point to a valid MIDI soundbank file. In general, MIDI
* soundbank providers may need to read some data from the stream before
* determining whether they support it. These parsers must be able to mark
* the stream, read enough data to determine whether they support the
* stream, and, if not, reset the stream's read pointer to its original
* position. If the input stream does not support this, this method may fail
* with an {@code IOException}.
*
* @param stream the source of the sound bank data
* @return the sound bank
* @throws InvalidMidiDataException if the stream does not point to valid
* MIDI soundbank data recognized by the system
* @throws IOException if an I/O error occurred when loading the soundbank
* @throws NullPointerException if {@code stream} is {@code null}
* @see InputStream#markSupported
* @see InputStream#mark
*/
public static Soundbank getSoundbank(final InputStream stream)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(stream);
SoundbankReader sp = null;
Soundbank s = null;
List
* This method and/or the code it invokes may need to read some data from
* the stream to determine whether its data format is supported. The
* implementation may therefore need to mark the stream, read enough data to
* determine whether it is in a supported format, and reset the stream's
* read pointer to its original position. If the input stream does not
* permit this set of operations, this method may fail with an
* {@code IOException}.
*
* This operation can only succeed for files of a type which can be parsed
* by an installed file reader. It may fail with an
* {@code InvalidMidiDataException} even for valid files if no compatible
* file reader is installed. It will also fail with an
* {@code InvalidMidiDataException} if a compatible file reader is
* installed, but encounters errors while determining the file format.
*
* @param stream the input stream from which file format information should
* be extracted
* @return an {@code MidiFileFormat} object describing the MIDI file format
* @throws InvalidMidiDataException if the stream does not point to valid
* MIDI file data recognized by the system
* @throws IOException if an I/O exception occurs while accessing the
* stream
* @throws NullPointerException if {@code stream} is {@code null}
* @see #getMidiFileFormat(URL)
* @see #getMidiFileFormat(File)
* @see InputStream#markSupported
* @see InputStream#mark
*/
public static MidiFileFormat getMidiFileFormat(final InputStream stream)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(stream);
List
* This operation can only succeed for files of a type which can be parsed
* by an installed file reader. It may fail with an
* {@code InvalidMidiDataException} even for valid files if no compatible
* file reader is installed. It will also fail with an
* {@code InvalidMidiDataException} if a compatible file reader is
* installed, but encounters errors while determining the file format.
*
* @param url the URL from which file format information should be
* extracted
* @return a {@code MidiFileFormat} object describing the MIDI file format
* @throws InvalidMidiDataException if the URL does not point to valid MIDI
* file data recognized by the system
* @throws IOException if an I/O exception occurs while accessing the URL
* @throws NullPointerException if {@code url} is {@code null}
* @see #getMidiFileFormat(InputStream)
* @see #getMidiFileFormat(File)
*/
public static MidiFileFormat getMidiFileFormat(final URL url)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(url);
List
* This operation can only succeed for files of a type which can be parsed
* by an installed file reader. It may fail with an
* {@code InvalidMidiDataException} even for valid files if no compatible
* file reader is installed. It will also fail with an
* {@code InvalidMidiDataException} if a compatible file reader is
* installed, but encounters errors while determining the file format.
*
* @param file the {@code File} from which file format information should
* be extracted
* @return a {@code MidiFileFormat} object describing the MIDI file format
* @throws InvalidMidiDataException if the {@code File} does not point to
* valid MIDI file data recognized by the system
* @throws IOException if an I/O exception occurs while accessing the file
* @throws NullPointerException if {@code file} is {@code null}
* @see #getMidiFileFormat(InputStream)
* @see #getMidiFileFormat(URL)
*/
public static MidiFileFormat getMidiFileFormat(final File file)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(file);
List
* This method and/or the code it invokes may need to read some data from
* the stream to determine whether its data format is supported. The
* implementation may therefore need to mark the stream, read enough data to
* determine whether it is in a supported format, and reset the stream's
* read pointer to its original position. If the input stream does not
* permit this set of operations, this method may fail with an
* {@code IOException}.
*
* This operation can only succeed for files of a type which can be parsed
* by an installed file reader. It may fail with an
* {@code InvalidMidiDataException} even for valid files if no compatible
* file reader is installed. It will also fail with an
* {@code InvalidMidiDataException} if a compatible file reader is
* installed, but encounters errors while constructing the {@code Sequence}
* object from the file data.
*
* @param stream the input stream from which the {@code Sequence} should be
* constructed
* @return a {@code Sequence} object based on the MIDI file data contained
* in the input stream
* @throws InvalidMidiDataException if the stream does not point to valid
* MIDI file data recognized by the system
* @throws IOException if an I/O exception occurs while accessing the stream
* @throws NullPointerException if {@code stream} is {@code null}
* @see InputStream#markSupported
* @see InputStream#mark
*/
public static Sequence getSequence(final InputStream stream)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(stream);
List
* This operation can only succeed for files of a type which can be parsed
* by an installed file reader. It may fail with an
* {@code InvalidMidiDataException} even for valid files if no compatible
* file reader is installed. It will also fail with an
* {@code InvalidMidiDataException} if a compatible file reader is
* installed, but encounters errors while constructing the {@code Sequence}
* object from the file data.
*
* @param url the URL from which the {@code Sequence} should be constructed
* @return a {@code Sequence} object based on the MIDI file data pointed to
* by the URL
* @throws InvalidMidiDataException if the URL does not point to valid MIDI
* file data recognized by the system
* @throws IOException if an I/O exception occurs while accessing the URL
* @throws NullPointerException if {@code url} is {@code null}
*/
public static Sequence getSequence(final URL url)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(url);
List
* This operation can only succeed for files of a type which can be parsed
* by an installed file reader. It may fail with an
* {@code InvalidMidiDataException} even for valid files if no compatible
* file reader is installed. It will also fail with an
* {@code InvalidMidiDataException} if a compatible file reader is
* installed, but encounters errors while constructing the {@code Sequence}
* object from the file data.
*
* @param file the {@code File} from which the {@code Sequence} should be
* constructed
* @return a {@code Sequence} object based on the MIDI file data pointed to
* by the File
* @throws InvalidMidiDataException if the File does not point to valid MIDI
* file data recognized by the system
* @throws IOException if an I/O exception occurs
* @throws NullPointerException if {@code file} is {@code null}
*/
public static Sequence getSequence(final File file)
throws InvalidMidiDataException, IOException {
Objects.requireNonNull(file);
List