FileDocCategorySizeDatePackage
PlayerPiano.javaAPI DocExample7161Sat Jan 24 10:44:40 GMT 2004je3.sound

PlayerPiano.java

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.com/javaexamples3.
 */
package je3.sound;
import java.io.*;
import javax.sound.midi.*;

public class PlayerPiano {
    // These are some MIDI constants from the spec.  They aren't defined
    // for us in javax.sound.midi.
    public static final int DAMPER_PEDAL = 64;
    public static final int DAMPER_ON = 127;
    public static final int DAMPER_OFF = 0;
    public static final int END_OF_TRACK = 47;

    public static void main(String[] args)
	throws MidiUnavailableException, InvalidMidiDataException, IOException
    {
	int instrument = 0;
	int tempo = 120;
	String filename = null;

	// Parse the options
	// -i <instrument number> default 0, a piano.  Allowed values: 0-127
	// -t <beats per minute>  default tempo is 120 quarter notes per minute
	// -o <filename>          save to a midi file instead of playing
	int a = 0; 
	while(a < args.length) {
	    if (args[a].equals("-i")) {
		instrument = Integer.parseInt(args[a+1]);
		a+=2;
	    }
	    else if (args[a].equals("-t")) {
		tempo = Integer.parseInt(args[a+1]);
		a+=2;
	    }
	    else if (args[a].equals("-o")) {
		filename = args[a+1];
		a += 2;
	    }
	    else break;
	}

	char[] notes = args[a].toCharArray();

	// 16 ticks per quarter note. 
	Sequence sequence = new Sequence(Sequence.PPQ, 16);

	// Add the specified notes to the track
	addTrack(sequence, instrument, tempo, notes);

	if (filename == null) {  // no filename, so play the notes
	    // Set up the Sequencer and Synthesizer objects
	    Sequencer sequencer = MidiSystem.getSequencer();
	    sequencer.open();  
	    Synthesizer synthesizer = MidiSystem.getSynthesizer();
	    synthesizer.open();
	    sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());

	    // Specify the sequence to play, and the tempo to play it at
	    sequencer.setSequence(sequence);
	    sequencer.setTempoInBPM(tempo);

	    // Let us know when it is done playing
	    sequencer.addMetaEventListener(new MetaEventListener() {
		    public void meta(MetaMessage m) {
			// A message of this type is automatically sent
			// when we reach the end of the track
			if (m.getType() == END_OF_TRACK) System.exit(0);
		    }
		});
	    // And start playing now.
	    sequencer.start();
	}
	else {  // A file name was specified, so save the notes
	    int[] allowedTypes = MidiSystem.getMidiFileTypes(sequence);
	    if (allowedTypes.length == 0) {
		System.err.println("No supported MIDI file types.");
	    }
	    else {
		MidiSystem.write(sequence, allowedTypes[0],
				 new File(filename));
		System.exit(0);
	    }
	}
    }

    static final int[] offsets = {  // add these amounts to the base value
	// A   B  C  D  E  F  G
	  -4, -2, 0, 1, 3, 5, 7  
    };

    /*
     * This method parses the specified char[] of notes into a Track.
     * The musical notation is the following:
     * A-G:   A named note; Add b for flat and # for sharp.
     * +:     Move up one octave. Persists.
     * -:     Move down one octave.  Persists.
     * /1:    Notes are whole notes.  Persists 'till changed
     * /2:    Half notes
     * /4:    Quarter notes
     * /n:    N can also be, 8, 16, 32, 64.
     * s:     Toggle sustain pedal on or off (initially off)
     * 
     * >:     Louder.  Persists
     * <:     Softer.  Persists
     * .:     Rest. Length depends on current length setting
     * Space: Play the previous note or notes; notes not separated by spaces
     *        are played at the same time
     */
    public static void addTrack(Sequence s, int instrument, int tempo,
				char[] notes)
	throws InvalidMidiDataException
    {
	Track track = s.createTrack();  // Begin with a new track

	// Set the instrument on channel 0
	ShortMessage sm = new ShortMessage();
	sm.setMessage(ShortMessage.PROGRAM_CHANGE, 0, instrument, 0);
	track.add(new MidiEvent(sm, 0));

	int n = 0; // current character in notes[] array
	int t = 0; // time in ticks for the composition

	// These values persist and apply to all notes 'till changed
	int notelength = 16; // default to quarter notes
	int velocity = 64;   // default to middle volume
	int basekey = 60;    // 60 is middle C. Adjusted up and down by octave
	boolean sustain = false;   // is the sustain pedal depressed?
	int numnotes = 0;    // How many notes in current chord?

	while(n < notes.length) {
	    char c = notes[n++];

	    if (c == '+') basekey += 12;        // increase octave
	    else if (c == '-') basekey -= 12;   // decrease octave
	    else if (c == '>') velocity += 16;  // increase volume;
	    else if (c == '<') velocity -= 16;  // decrease volume;
	    else if (c == '/') {
		char d = notes[n++];
		if (d == '2') notelength = 32;  // half note
		else if (d == '4') notelength = 16;  // quarter note
		else if (d == '8') notelength = 8;   // eighth note
		else if (d == '3' && notes[n++] == '2') notelength = 2;
		else if (d == '6' && notes[n++] == '4') notelength = 1;
		else if (d == '1') {
		    if (n < notes.length && notes[n] == '6')
			notelength = 4;    // 1/16th note
		    else notelength = 64;  // whole note
		}
	    }
	    else if (c == 's') {
		sustain = !sustain;
		// Change the sustain setting for channel 0
		ShortMessage m = new ShortMessage();
		m.setMessage(ShortMessage.CONTROL_CHANGE, 0,
			     DAMPER_PEDAL, sustain?DAMPER_ON:DAMPER_OFF);
		track.add(new MidiEvent(m, t));
	    }
	    else if (c >= 'A' && c <= 'G') {
		int key = basekey + offsets[c - 'A'];
		if (n < notes.length) {
		    if (notes[n] == 'b') { // flat
			key--; 
			n++;
		    }
		    else if (notes[n] == '#') { // sharp
			key++;
			n++;
		    }
		}

		addNote(track, t, notelength, key, velocity);
		numnotes++;
	    }
	    else if (c == ' ') {
		// Spaces separate groups of notes played at the same time.
		// But we ignore them unless they follow a note or notes.
		if (numnotes > 0) {
		    t += notelength;
		    numnotes = 0;
		}
	    }
	    else if (c == '.') { 
		// Rests are like spaces in that they force any previous
		// note to be output (since they are never part of chords)
		if (numnotes > 0) {
		    t += notelength;
		    numnotes = 0;
		}
		// Now add additional rest time
		t += notelength;
	    }
	}
    }
	
    // A convenience method to add a note to the track on channel 0
    public static void addNote(Track track, int startTick,
			       int tickLength, int key, int velocity)
	throws InvalidMidiDataException
    {
	ShortMessage on = new ShortMessage();
	on.setMessage(ShortMessage.NOTE_ON,  0, key, velocity);
	ShortMessage off = new ShortMessage();
	off.setMessage(ShortMessage.NOTE_OFF, 0, key, velocity);
	track.add(new MidiEvent(on, startTick));
	track.add(new MidiEvent(off, startTick + tickLength));
    }
}