/*
* JFugue - API for Music Programming
* Copyright (C) 2003-2008 David Koelle
*
* http://www.jfugue.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.jfugue;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiFileFormat;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
/**
* Prepares a pattern to be turned into music by the Renderer. This class also
* handles saving the sequence derived from a pattern as a MIDI file.
*
* @see MidiRenderer
* @see Pattern
* @author David Koelle
* @version 2.0
*/
public class Player {
private Sequencer sequencer;
private MusicStringParser parser;
private MidiRenderer renderer;
private float sequenceTiming = Sequence.PPQ;
private int resolution = 120;
private boolean paused = false;
private boolean started = false;
private boolean finished = false;
/**
* Instantiates a new Player object, which is used for playing music.
*/
public Player() {
this(true);
}
/**
* Instantiates a new Player object, which is used for playing music. The
* <code>connected</code> parameter is passed directly to
* MidiSystem.getSequencer. Pass false when you do not want to copy a live
* synthesizer - for example, if your Player is on a server, and you don't
* want to create new synthesizers every time the constructor is called.
*/
public Player(boolean connected) {
try {
// Get default sequencer.
setSequencer(MidiSystem.getSequencer(connected)); // use non
// connected
// sequencer so
// no copy of
// live
// synthesizer
// will be
// created.
} catch (MidiUnavailableException e) {
throw new JFugueException(
JFugueException.SEQUENCER_DEVICE_NOT_SUPPORTED_WITH_EXCEPTION
+ e.getMessage());
}
initParser();
}
/**
* Creates a new Player instance using a Sequencer that you have provided.
*
* @param sequencer
* The Sequencer to send the MIDI events
*/
public Player(Sequencer sequencer) {
setSequencer(sequencer);
initParser();
}
/**
* Creates a new Player instance using a Sequencer obtained from the
* Synthesizer that you have provided.
*
* @param synth
* The Synthesizer you want to use for this Player.
*/
public Player(Synthesizer synth) throws MidiUnavailableException {
this(Player.getSequencerConnectedToSynthesizer(synth));
}
private void initParser() {
this.parser = new MusicStringParser();
this.renderer = new MidiRenderer(sequenceTiming, resolution);
this.parser.addParserListener(this.renderer);
}
private void initSequencer() {
// Close the sequencer and synthesizer
getSequencer().addMetaEventListener(new MetaEventListener() {
@Override
public void meta(MetaMessage event) {
if (event.getType() == 47) {
close();
}
}
});
}
private void openSequencer() {
if (getSequencer() == null) {
throw new JFugueException(
JFugueException.SEQUENCER_DEVICE_NOT_SUPPORTED);
}
// Open the sequencer, if it is not already open
if (!getSequencer().isOpen()) {
try {
getSequencer().open();
} catch (MidiUnavailableException e) {
throw new JFugueException(
JFugueException.SEQUENCER_DEVICE_NOT_SUPPORTED_WITH_EXCEPTION
+ e.getMessage());
}
}
}
/**
* Plays a pattern by setting up a Renderer and feeding the pattern to it.
*
* @param pattern
* the pattern to play
* @see MidiRenderer
*/
public void play(Pattern pattern) {
Sequence sequence = getSequence(pattern);
play(sequence);
}
/**
* Plays a pattern by setting up a Renderer and feeding the pattern to it.
*
* @param pattern
* the pattern to play
* @see MidiRenderer
*/
public void play(Rhythm rhythm) {
Pattern pattern = rhythm.getPattern();
Sequence sequence = getSequence(pattern);
play(sequence);
}
/**
* Plays a MIDI Sequence
*
* @param sequence
* the Sequence to play
* @throws JFugueException
* if there is a problem playing the music
* @see MidiRenderer
*/
private void play(Sequence sequence) {
// Open the sequencer
openSequencer();
// Set the sequence
try {
getSequencer().setSequence(sequence);
} catch (Exception e) {
throw new JFugueException(
JFugueException.ERROR_PLAYING_MUSIC + e.getMessage());
}
setStarted(true);
// Start the sequence
getSequencer().start();
// Wait for the sequence to finish
while (isPlaying() || isPaused()) {
try {
Thread.sleep(20); // don't hog all of the CPU
} catch (InterruptedException e) {
throw new JFugueException(JFugueException.ERROR_SLEEP);
}
}
// Close the sequencer
getSequencer().close();
setStarted(false);
setFinished(true);
}
/**
* Plays a string of music. Be sure to call player.close() after play() has
* returned.
*
* @param musicString
* the MusicString (JFugue-formatted string) to play
* @version 3.0
*/
public void play(String musicString) {
if (musicString.indexOf(".mid") > 0) {
// If the user tried to call this method with "filename.mid" or
// "filename.midi", throw the following exception
throw new JFugueException(
JFugueException.PLAYS_STRING_NOT_FILE_EXC);
}
Pattern pattern = new Pattern(musicString);
play(pattern);
}
/**
* Plays a MIDI file, without doing any conversions to MusicStrings. Be sure
* to call player.close() after play() has returned.
*
* @param file
* the MIDI file to play
* @throws IOException
* @throws InvalidMidiDataException
* @version 3.0
*/
public void playMidiDirectly(File file)
throws IOException, InvalidMidiDataException {
Sequence sequence = MidiSystem.getSequence(file);
play(sequence);
}
/**
* Plays a URL that contains a MIDI sequence. Be sure to call player.close()
* after play() has returned.
*
* @param url
* the URL to play
* @throws IOException
* @throws InvalidMidiDataException
* @version 3.0
*/
public void playMidiDirectly(URL url)
throws IOException, InvalidMidiDataException {
Sequence sequence = MidiSystem.getSequence(url);
play(sequence);
}
public void play(Anticipator anticipator, Pattern pattern, long offset) {
Sequence sequence = getSequence(pattern);
Sequence sequence2 = getSequence(pattern);
play(anticipator, sequence, sequence2, offset);
}
public void play(Anticipator anticipator, Sequence sequence,
Sequence sequence2, long offset) {
anticipator.play(sequence);
if (offset > 0) {
try {
Thread.sleep(offset);
} catch (InterruptedException e) {
throw new JFugueException(JFugueException.ERROR_SLEEP);
}
}
play(sequence2);
}
/**
* Closes MIDI resources - be sure to call this after play() has returned.
*/
public void close() {
getSequencer().close();
try {
if (MidiSystem.getSynthesizer() != null) {
MidiSystem.getSynthesizer().close();
}
} catch (MidiUnavailableException e) {
throw new JFugueException(
JFugueException.GENERAL_ERROR + e.getMessage());
}
}
private void setStarted(boolean started) {
this.started = started;
}
private void setFinished(boolean finished) {
this.finished = finished;
}
public boolean isStarted() {
return this.started;
}
public boolean isFinished() {
return this.finished;
}
public boolean isPlaying() {
return getSequencer().isRunning();
}
public boolean isPaused() {
return paused;
}
public void pause() {
paused = true;
if (isPlaying()) {
getSequencer().stop();
}
}
public void resume() {
paused = false;
getSequencer().start();
}
public void stop() {
paused = false;
getSequencer().stop();
getSequencer().setMicrosecondPosition(0);
}
public void jumpTo(long microseconds) {
getSequencer().setMicrosecondPosition(microseconds);
}
public long getSequenceLength(Sequence sequence) {
return sequence.getMicrosecondLength();
}
public long getSequencePosition() {
return getSequencer().getMicrosecondPosition();
}
/**
* Saves the MIDI data from a pattern into a file.
*
* @param pattern
* the pattern to save
* @param file
* the File to save the pattern to. Should include file
* extension, such as .mid
*/
public void saveMidi(Pattern pattern, File file) throws IOException {
Sequence sequence = getSequence(pattern);
int[] writers = MidiSystem.getMidiFileTypes(sequence);
if (writers.length == 0) {
return;
}
MidiSystem.write(sequence, writers[0], file);
}
/**
* Saves the MIDI data from a MusicString into a file.
*
* @param musicString
* the MusicString to save
* @param file
* the File to save the MusicString to. Should include file
* extension, such as .mid
*/
public void saveMidi(String musicString, File file) throws IOException {
Pattern pattern = new Pattern(musicString);
saveMidi(pattern, file);
}
/**
* Parses a MIDI file and returns a Pattern. This is an excellent example of
* JFugue's Parser-Renderer architecture:
*
* <pre>
* MidiParser parser = new MidiParser();
* MusicStringRenderer renderer = new MusicStringRenderer();
* parser.addParserListener(renderer);
* parser.parse(sequence);
* </pre>
*
* @param filename
* The name of the MIDI file
* @return a Pattern containing the MusicString representing the MIDI music
* @throws IOException
* If there is a problem opening the MIDI file
* @throws InvalidMidiDataException
* If there is a problem obtaining MIDI resources
*/
public Pattern loadMidi(File file)
throws IOException, InvalidMidiDataException {
MidiFileFormat format = MidiSystem.getMidiFileFormat(file);
this.sequenceTiming = format.getDivisionType();
this.resolution = format.getResolution();
return Pattern.loadMidi(file);
}
public static void allNotesOff() {
try {
allNotesOff(MidiSystem.getSynthesizer());
} catch (MidiUnavailableException e) {
throw new JFugueException(JFugueException.GENERAL_ERROR);
}
}
/**
* Stops all notes from playing on all MIDI channels.
*/
public static void allNotesOff(Synthesizer synth) {
try {
if (!synth.isOpen()) {
synth.open();
}
MidiChannel[] channels = synth.getChannels();
for (int i = 0; i < channels.length; i++) {
channels[i].allNotesOff();
}
} catch (MidiUnavailableException e) {
throw new JFugueException(JFugueException.GENERAL_ERROR);
}
}
/**
* Returns the sequencer containing the MIDI data from a pattern that has
* been parsed.
*
* @return the Sequencer from the pattern that was recently parsed
*/
public Sequencer getSequencer() {
return this.sequencer;
}
private void setSequencer(Sequencer sequencer) {
this.sequencer = sequencer;
initSequencer();
}
/**
* Returns the sequence containing the MIDI data from the given pattern.
*
* @return the Sequence from the given pattern
*/
public Sequence getSequence(Pattern pattern) {
this.renderer.reset();
this.parser.parse(pattern);
Sequence sequence = this.renderer.getSequence();
return sequence;
}
/**
* Returns an instance of a Sequencer that uses the provided Synthesizer as
* its receiver. This is useful when you have made changes to a specific
* Synthesizer--for example, you've loaded in new patches--that you want the
* Sequencer to use. You can then pass the Sequencer to the Player
* constructor.
*
* @param synth
* The Synthesizer to use as the receiver for the returned
* Sequencer
* @return a Sequencer with the provided Synthesizer as its receiver
* @throws MidiUnavailableException
* @version 4.0
*/
public static Sequencer getSequencerConnectedToSynthesizer(
Synthesizer synth) throws MidiUnavailableException {
Sequencer sequencer = MidiSystem.getSequencer(false); // Get Sequencer
// which is not
// connected to
// new
// Synthesizer.
sequencer.open();
if (!synth.isOpen()) {
synth.open();
}
sequencer.getTransmitter().setReceiver(synth.getReceiver()); // Connect
// the
// Synthesizer
// to
// our
// synthesizer
// instance.
return sequencer;
}
}