/*
* 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() {
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;
}
}