import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import java.io.File;
/**
* Plays one or more notes through Midi. If you find yourself wanting to use lots of individual
* sound files for a scenario, such as emulating an instrument for example, you may find using
* midi a better choice. It has several advantages over using lots of wav files:
*
* - It takes up far less space, so less disk space is used and loading times are reduced
* - You can switch between instruments really easily if you wish
* - You can adjust things like the length of time notes are held for really easily
* - You can generally manipulate the details of midi notes far more easily than you can with actual sound files
* - If you really want to be clever, you can add pitch bend, adjust how quickly notes are played and more!
*
* However, Midi is not always suited to the task - the sounds aren't always the same on each
* computer (since they're generated through the sound card and not in advance) and the quality
* won't be as good as a recorded sound. If you're looking for special effects, a backing track
* or in game sounds then chances are you'll be better off using normal sound files.
*
* @author Michael Berry (mjrb4)
* @version 12/02/09
*/
public class MidiPlayer
{
/** The default instrument to use if one isn't specified in the constructor.*/
public static int DEFAULT_INSTRUMENT = 4;
/** The channel used to play the Midi notes. */
private MidiChannel channel;
/** A sequencer used to play Midi files. */
private static Sequencer sequencer;
/**
* Try to set the seqencer up.
*/
{
try {
sequencer = MidiSystem.getSequencer();
}
catch(Exception ex) {
ex.printStackTrace();
}
}
/**
* Create a new MidiPlayer object with a default
* instrument specified by DEFAULT_INSTRUMENT -
* usually a piano sound.
*/
public MidiPlayer()
{
channel = getChannel(DEFAULT_INSTRUMENT);
}
/**
* Create a new MidiPlayer object with a specified
* instrument.
* @param instrument the instrument to use
*/
public MidiPlayer(int instrument)
{
channel = getChannel(instrument);
}
/**
* Change the instrument this MidiPlayer uses.
* @param instrument the instrument to change to
*/
public void setInstrument(int instrument)
{
channel.programChange(instrument);
}
/**
* Get the instrument the MidiPlayer is using
* at present.
* @return the instrument in use.
*/
public int getInstrument()
{
return channel.getProgram();
}
/**
* Converts a string description of the key
* to the number used by the MidiPlayer.
* @throws RuntimeException if the key name is invalid.
*/
public int getNumber(String key)
{
try {
String note = new Character(key.charAt(0)).toString();
boolean accidental = false;
if(key.charAt(1)=='b') {
note += "b";
accidental = true;
}
else if(key.charAt(1)=='#') {
note += "#";
accidental = true;
}
int offset = 1;
if(accidental) offset = 2;
int number = Integer.parseInt(key.substring(offset));
int midiNum = (number+1)*12;
midiNum += getOffset(note);
return midiNum;
}
catch(Exception ex) {
throw new RuntimeException(key + " is an invalid key name...");
}
}
/**
* Set how far each note is (relatively in semitones) above C.
*/
private int getOffset(String note)
{
if(note.equalsIgnoreCase("C")) return 0;
if(note.equalsIgnoreCase("C#")) return 1;
if(note.equalsIgnoreCase("Db")) return 1;
if(note.equalsIgnoreCase("D")) return 2;
if(note.equalsIgnoreCase("D#")) return 3;
if(note.equalsIgnoreCase("Eb")) return 3;
if(note.equalsIgnoreCase("E")) return 4;
if(note.equalsIgnoreCase("E#")) return 5;
if(note.equalsIgnoreCase("F")) return 5;
if(note.equalsIgnoreCase("F#")) return 6;
if(note.equalsIgnoreCase("Gb")) return 6;
if(note.equalsIgnoreCase("G")) return 7;
if(note.equalsIgnoreCase("G#")) return 8;
if(note.equalsIgnoreCase("Ab")) return 8;
if(note.equalsIgnoreCase("A")) return 9;
if(note.equalsIgnoreCase("A#")) return 10;
if(note.equalsIgnoreCase("Bb")) return 10;
if(note.equalsIgnoreCase("B")) return 11;
if(note.equalsIgnoreCase("Cb")) return 11;
else throw new RuntimeException();
}
/**
* Play a note - this method doesn't turn the note
* off after a specified period of time, the release
* method must be called to do that.
* @param note the note to play
*/
public void play(final int note)
{
channel.noteOn(note, 50);
}
/**
* Release a note that was previously played. If this
* note isn't on already, this method will do nothing.
*/
public void release(final int note)
{
channel.noteOff(note, 50);
}
/**
* Play a note for a certain amount of time.
* @param note the integer value for the note to play
* @param length the length to play the note (ms).
*/
public void play(final int note, final int length)
{
new Thread() {
public void run() {
channel.noteOn(note, 50);
try {
Thread.sleep(length);
}
catch(InterruptedException ex) {}
finally {
channel.noteOff(note, 50);
}
channel.noteOff(note, 50);
}
}.start();
}
/**
* Release all notes smoothly. This can be called in the World.stopped()
* method to ensure no notes are playing when the scenario has been
* stopped or reset.
*/
public void turnAllOff()
{
try {
channel.allNotesOff();
//This would turn cut notes of immediately and suddenly:
//channel.allSoundOff();
sequencer.stop();
sequencer.close();
}
catch(Exception ex) {}
}
/**
* Get the MidiChannel object that this MidiPlayer class is using.
* If you want to do some more advanced work with the midi channel, you
* can use this method to get the MidiChannel object and then work with
* it directly. The API for the MidiChannel class is available online
* as part of the javax.sound.midi package.
* Examples of why you might want to use this - adjusting the speed
* notes are played / released with, adding sustain, adding pitch
* bend, soloing / muting individual channels - all are fairly advanced
* features and as such are not included in this class as standard (to
* keep things simple and avoid clutter.)
* @return the MidiChannel object behind this MidiPlayer.
*/
public MidiChannel getMidiChannel()
{
return channel;
}
/**
* Play a Midi file.
*/
public static void playMidiFile(String fileName)
{
try {
Sequence sequence = MidiSystem.getSequence(new File(fileName));
sequencer.open();
sequencer.setSequence(sequence);
sequencer.start();
}
catch(Exception ex) {
ex.printStackTrace();
}
}
/**
* Stop playing a Midi file.
*/
public static void stopMidiFile()
{
sequencer.stop();
}
/**
* Internal method to get the channel from the synthesizer in use.
* @param instrument the instrument to load initially.
*/
private MidiChannel getChannel(int instrument)
{
try {
Synthesizer synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
for (int i=0; i