Upto: Table of Contents of full book "Programming and Using Linux Sound"

Java Sound

JavaSound has a very well developed MIDI system, with cleanly separated components such as Sequencers and Synthesizers, and also allows hooks for Meta events as well as ordinary MIDI events.

Introduction

Java Sound has been around since early days of Java. It deals with both sampled and MIDI data. This chapter considers programming using the MIDI API.

Resources

There are many resources available for Java Sound:

Key JavaSound MIDI classes

Device Information

Device information is found by querying MidiSystem for its list of DeviceInfo objects. Each information object contains fields such as Name and Vendor. The actual device may be found using this information object by MidiSystem.getMidiDevice(info). The device may then be queried for its receivers and transmitters and its type as sequencer or synthesizer.

One annoying part is that you cannot get a list of all the devices's transmitters and receivers, only those that are open. You can ask for the default transmitter and receiver which will implicitly open them. So you can see that the list may be empty before asking for the default, but will be non-empty afterwards if there is a default! if there are no defaults, a MidiUnavailableException exception will be thrown.

The program is:

	
      

import javax.sound.midi.*;
import java.util.*;

public class DeviceInfo {

    public static void main(String[] args) throws Exception {
	MidiDevice.Info[] devices;

	/*
	MidiDevice.Info[] info = p.getDeviceInfo();
	for (int m = 0; m < info.length; m++) {
	    System.out.println(info[m].toString());
	}
	*/

	System.out.println("MIDI devices:");
	devices = MidiSystem.getMidiDeviceInfo();
	for (MidiDevice.Info info: devices) {
	    System.out.println("    Name: " + info.toString() + 
			       ", Decription: " +
			       info.getDescription() + 
			       ", Vendor: " +
			       info.getVendor());
	    MidiDevice device = MidiSystem.getMidiDevice(info);
	    if (! device.isOpen()) {
		device.open();
	    }
	    if (device instanceof Sequencer) {
		System.out.println("        Device is a sequencer");
	    }
	    if (device instanceof Synthesizer) {
		System.out.println("        Device is a synthesizer");
	    }
	    System.out.println("        Open receivers:");
	    List<Receiver> receivers = device.getReceivers();
	    for (Receiver r: receivers) {
		System.out.println("            " + r.toString());
	    }
	    try {
		System.out.println("\n        Default receiver: " + 
				   device.getReceiver().toString());

		System.out.println("\n        Open receivers now:");
		receivers = device.getReceivers();
		for (Receiver r: receivers) {
		    System.out.println("            " + r.toString());
		}
	    } catch(MidiUnavailableException e) {
		System.out.println("        No default receiver");
	    }
	
	    System.out.println("\n        Open transmitters:");
	    List<Transmitter> transmitters = device.getTransmitters();
	    for (Transmitter t: transmitters) {
		System.out.println("            " + t.toString());
	    }
	    try {
		System.out.println("\n        Default transmitter: " + 
				   device.getTransmitter().toString());

		System.out.println("\n        Open transmitters now:");
		transmitters = device.getTransmitters();
		for (Transmitter t: transmitters) {
		    System.out.println("            " + t.toString());
		}
	    } catch(MidiUnavailableException e) {
		System.out.println("        No default transmitter");
	    }
	    device.close();
	}

	
	Sequencer sequencer = MidiSystem.getSequencer();
	System.out.println("Default system sequencer is " + 
			   sequencer.getDeviceInfo().toString() +
			   " (" + sequencer.getClass() + ")");

	Synthesizer synthesizer = MidiSystem.getSynthesizer();
	System.out.println("Default system synthesizer is " + 
			   synthesizer.getDeviceInfo().toString() +
			   " (" + synthesizer.getClass() + ")");

    }
}

	
      

The output on my system is

	
MIDI devices:
    Name: Gervill, Decription: Software MIDI Synthesizer, Vendor: OpenJDK
        Device is a synthesizer
        Open receivers:

        Default receiver: com.sun.media.sound.SoftReceiver@72f2a824

        Open receivers now:
            com.sun.media.sound.SoftReceiver@72f2a824

        Open transmitters:
        No default transmitter
    Name: Real Time Sequencer, Decription: Software sequencer, Vendor: Oracle Corporation
        Device is a sequencer
        Open receivers:

        Default receiver: com.sun.media.sound.RealTimeSequencer$SequencerReceiver@c23c5ff

        Open receivers now:
            com.sun.media.sound.RealTimeSequencer$SequencerReceiver@c23c5ff

        Open transmitters:
        Default transmitter: com.sun.media.sound.RealTimeSequencer$SequencerTransmitter@4e13aa4e

        Open transmitters now:
            com.sun.media.sound.RealTimeSequencer$SequencerTransmitter@4e13aa4e
Default system sequencer is Real Time Sequencer
Default system synthesizer is Gervill
	
      

Dumping a MIDI file

These two programs from jsresources.org dump a MIDI file to the console. The MidiSystem creates a Sequence from a file. Each track of the sequence is looped through and each event within each track is examined. While it would be possible to print in situ, each event is passed to a Receiver object which in this case is DumpReceiver. That object could do anything, but in this case just prints the event to stdout.

DumpSequence.java is

      
      /*
 *	DumpSequence.java
 *
 *	This file is part of jsresources.org
 */

/*
 * Copyright (c) 1999, 2000 by Matthias Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
  |<---            this code is formatted to fit into 80 columns             --->|
*/

import java.io.File;
import java.io.IOException;

import javax.sound.midi.MidiSystem;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Track;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.SysexMessage;
import javax.sound.midi.Receiver;




/**	<titleabbrev>DumpSequence</titleabbrev>
	<title>Displaying the content of a MIDI file</title>

	<formalpara><title>Purpose</title>
	<para>Dumps the decoded content of a MIDI file to the console.</para>
	</formalpara>

	<formalpara><title>Usage</title>
	<para>
	<cmdsynopsis><command>java DumpSequence</command>
	<arg choice="plain"><replaceable class="parameter">midifile</replaceable></arg>
	</cmdsynopsis>
	</para></formalpara>

	<formalpara><title>Parameters</title>
	<variablelist>
	<varlistentry>
	<term><replaceable class="parameter">midifile</replaceable></term>
	<listitem><para>the filename of the MIDI file that should be displayed</para></listitem>
	</varlistentry>
	</variablelist>
	</formalpara>

	<formalpara><title>Bugs, limitations</title>
	<para>Meta and system common events are not displayed in detail.</para>
	</formalpara>

	<formalpara><title>Source code</title>
	<para>
	<ulink url="DumpSequence.java.html">DumpSequence.java</ulink>,
	<ulink url="DumpReceiver.java.html">DumpReceiver.java</ulink>
	</para>
	</formalpara>

*/
public class DumpSequence
{
    private static String[]	sm_astrKeyNames = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};

    private static Receiver		sm_receiver = new DumpReceiver(System.out, true);




    public static void main(String[] args)
    {
	/*
	 *	We check that there is exactely one command-line
	 *	argument. If not, we display the usage message and
	 *	exit.
	 */
	if (args.length != 1)
	    {
		out("DumpSequence: usage:");
		out("\tjava DumpSequence <midifile>");
		System.exit(1);
	    }
	/*
	 *	Now, that we're shure there is an argument, we take it as
	 *	the filename of the soundfile we want to play.
	 */
	String	strFilename = args[0];
	File	midiFile = new File(strFilename);

	/*
	 *	We try to get a Sequence object, which the content
	 *	of the MIDI file.
	 */
	Sequence	sequence = null;
	try
	    {
		sequence = MidiSystem.getSequence(midiFile);
	    }
	catch (InvalidMidiDataException e)
	    {
		e.printStackTrace();
		System.exit(1);
	    }
	catch (IOException e)
	    {
		e.printStackTrace();
		System.exit(1);
	    }

	/*
	 *	And now, we output the data.
	 */
	if (sequence == null)
	    {
		out("Cannot retrieve Sequence.");
	    }
	else
	    {
		out("---------------------------------------------------------------------------");
		out("File: " + strFilename);
		out("---------------------------------------------------------------------------");
		out("Length: " + sequence.getTickLength() + " ticks");
		out("Duration: " + sequence.getMicrosecondLength() + " microseconds");
		out("---------------------------------------------------------------------------");
		float	fDivisionType = sequence.getDivisionType();
		String	strDivisionType = null;
		if (fDivisionType == Sequence.PPQ)
		    {
			strDivisionType = "PPQ";
		    }
		else if (fDivisionType == Sequence.SMPTE_24)
		    {
			strDivisionType = "SMPTE, 24 frames per second";
		    }
		else if (fDivisionType == Sequence.SMPTE_25)
		    {
			strDivisionType = "SMPTE, 25 frames per second";
		    }
		else if (fDivisionType == Sequence.SMPTE_30DROP)
		    {
			strDivisionType = "SMPTE, 29.97 frames per second";
		    }
		else if (fDivisionType == Sequence.SMPTE_30)
		    {
			strDivisionType = "SMPTE, 30 frames per second";
		    }

		out("DivisionType: " + strDivisionType);

		String	strResolutionType = null;
		if (sequence.getDivisionType() == Sequence.PPQ)
		    {
			strResolutionType = " ticks per beat";
		    }
		else
		    {
			strResolutionType = " ticks per frame";
		    }
		out("Resolution: " + sequence.getResolution() + strResolutionType);
		out("---------------------------------------------------------------------------");
		Track[]	tracks = sequence.getTracks();
		for (int nTrack = 0; nTrack < tracks.length; nTrack++)
		    {
			out("Track " + nTrack + ":");
			out("-----------------------");
			Track	track = tracks[nTrack];
			for (int nEvent = 0; nEvent < track.size(); nEvent++)
			    {
				MidiEvent	event = track.get(nEvent);
				output(event);
			    }
			out("---------------------------------------------------------------------------");
		    }
		// TODO: getPatchList()
	    }
    }


    public static void output(MidiEvent event)
    {
	MidiMessage	message = event.getMessage();
	long		lTicks = event.getTick();
	sm_receiver.send(message, lTicks);
    }



    private static void out(String strMessage)
    {
	System.out.println(strMessage);
    }
}



/*** DumpSequence.java ***/


      
    

DmpReceiver.java is

      
      /*
 *	DumpReceiver.java
 *
 *	This file is part of jsresources.org
 */

/*
 * Copyright (c) 1999 - 2001 by Matthias Pfisterer
 * Copyright (c) 2003 by Florian Bomers
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import	java.io.PrintStream;

import	javax.sound.midi.MidiSystem;
import	javax.sound.midi.InvalidMidiDataException;
import	javax.sound.midi.Sequence;
import	javax.sound.midi.Track;
import	javax.sound.midi.MidiEvent;
import	javax.sound.midi.MidiMessage;
import	javax.sound.midi.ShortMessage;
import	javax.sound.midi.MetaMessage;
import	javax.sound.midi.SysexMessage;
import	javax.sound.midi.Receiver;



/**	Displays the file format information of a MIDI file.
 */
public class DumpReceiver
	implements	Receiver
{

	public static long seByteCount = 0;
	public static long smByteCount = 0;
	public static long seCount = 0;
	public static long smCount = 0;

	private static final String[]		sm_astrKeyNames = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};

	private static final String[]		sm_astrKeySignatures = {"Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#"};
	private static final String[]		SYSTEM_MESSAGE_TEXT =
	{
		"System Exclusive (should not be in ShortMessage!)",
		"MTC Quarter Frame: ",
		"Song Position: ",
		"Song Select: ",
		"Undefined",
		"Undefined",
		"Tune Request",
		"End of SysEx (should not be in ShortMessage!)",
		"Timing clock",
		"Undefined",
		"Start",
		"Continue",
		"Stop",
		"Undefined",
		"Active Sensing",
		"System Reset"
	};

	private static final String[]		QUARTER_FRAME_MESSAGE_TEXT =
	{
		"frame count LS: ",
		"frame count MS: ",
		"seconds count LS: ",
		"seconds count MS: ",
		"minutes count LS: ",
		"minutes count MS: ",
		"hours count LS: ",
		"hours count MS: "
	};

	private static final String[]		FRAME_TYPE_TEXT =
	{
		"24 frames/second",
		"25 frames/second",
		"30 frames/second (drop)",
		"30 frames/second (non-drop)",
	};

	private PrintStream		m_printStream;
	private boolean			m_bDebug;
	private boolean			m_bPrintTimeStampAsTicks;



	public DumpReceiver(PrintStream printStream)
	{
		this(printStream, false);
	}


	public DumpReceiver(PrintStream printStream,
			    boolean bPrintTimeStampAsTicks)
	{
		m_printStream = printStream;
		m_bDebug = false;
		m_bPrintTimeStampAsTicks = bPrintTimeStampAsTicks;
	}



	public void close()
	{
	}



	public void send(MidiMessage message, long lTimeStamp)
	{
		String	strMessage = null;
		if (message instanceof ShortMessage)
		{
			strMessage = decodeMessage((ShortMessage) message);
		}
		else if (message instanceof SysexMessage)
		{
			strMessage = decodeMessage((SysexMessage) message);
		}
		else if (message instanceof MetaMessage)
		{
			strMessage = decodeMessage((MetaMessage) message);
		}
		else
		{
			strMessage = "unknown message type";
		}
		String	strTimeStamp = null;
		if (m_bPrintTimeStampAsTicks)
		{
			strTimeStamp = "tick " + lTimeStamp + ": ";
		}
		else
		{
			if (lTimeStamp == -1L)
			{
				strTimeStamp = "timestamp [unknown]: ";
			}
			else
			{
				strTimeStamp = "timestamp " + lTimeStamp + " us: ";
			}
		}
		m_printStream.println(strTimeStamp + strMessage);
	}



	public String decodeMessage(ShortMessage message)
	{
		String	strMessage = null;
		switch (message.getCommand())
		{
		case 0x80:
			strMessage = "note Off " + getKeyName(message.getData1()) + " velocity: " + message.getData2();
			break;

		case 0x90:
			strMessage = "note On " + getKeyName(message.getData1()) + " velocity: " + message.getData2();
			break;

		case 0xa0:
			strMessage = "polyphonic key pressure " + getKeyName(message.getData1()) + " pressure: " + message.getData2();
			break;

		case 0xb0:
			strMessage = "control change " + message.getData1() + " value: " + message.getData2();
			break;

		case 0xc0:
			strMessage = "program change " + message.getData1();
			break;

		case 0xd0:
			strMessage = "key pressure " + getKeyName(message.getData1()) + " pressure: " + message.getData2();
			break;

		case 0xe0:
			strMessage = "pitch wheel change " + get14bitValue(message.getData1(), message.getData2());
			break;

		case 0xF0:
			strMessage = SYSTEM_MESSAGE_TEXT[message.getChannel()];
			switch (message.getChannel())
			{
			case 0x1:
				int	nQType = (message.getData1() & 0x70) >> 4;
				int	nQData = message.getData1() & 0x0F;
				if (nQType == 7)
				{
					nQData = nQData & 0x1;
				}
				strMessage += QUARTER_FRAME_MESSAGE_TEXT[nQType] + nQData;
				if (nQType == 7)
				{
					int	nFrameType = (message.getData1() & 0x06) >> 1;
					strMessage += ", frame type: " + FRAME_TYPE_TEXT[nFrameType];
				}
				break;

			case 0x2:
				strMessage += get14bitValue(message.getData1(), message.getData2());
				break;

			case 0x3:
				strMessage += message.getData1();
				break;
			}
			break;

		default:
			strMessage = "unknown message: status = " + message.getStatus() + ", byte1 = " + message.getData1() + ", byte2 = " + message.getData2();
			break;
		}
		if (message.getCommand() != 0xF0)
		{
			int	nChannel = message.getChannel() + 1;
			String	strChannel = "channel " + nChannel + ": ";
			strMessage = strChannel + strMessage;
		}
		smCount++;
		smByteCount+=message.getLength();
		return "["+getHexString(message)+"] "+strMessage;
	}



	public String decodeMessage(SysexMessage message)
	{
		byte[]	abData = message.getData();
		String	strMessage = null;
		// System.out.println("sysex status: " + message.getStatus());
		if (message.getStatus() == SysexMessage.SYSTEM_EXCLUSIVE)
		{
			strMessage = "Sysex message: F0" + getHexString(abData);
		}
		else if (message.getStatus() == SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE)
		{
			strMessage = "Continued Sysex message F7" + getHexString(abData);
			seByteCount--; // do not count the F7
		}
		seByteCount += abData.length + 1;
		seCount++; // for the status byte
		return strMessage;
	}



	public String decodeMessage(MetaMessage message)
	{
		byte[]	abMessage = message.getMessage();
		byte[]	abData = message.getData();
		int	nDataLength = message.getLength();
		String	strMessage = null;
		//System.out.println("data array length: " + abData.length);
		switch (message.getType())
		{
		case 0:
		        int	nSequenceNumber;
		        if (abData.length == 0)
			    nSequenceNumber = 0;
		        else
			    nSequenceNumber = ((abData[0] & 0xFF) << 8) | (abData[1] & 0xFF);
			strMessage = "Sequence Number: " + nSequenceNumber;
			break;

		case 1:
			String	strText = new String(abData);
			strMessage = "Text Event: " + strText;
			break;

		case 2:
			String	strCopyrightText = new String(abData);
			strMessage = "Copyright Notice: " + strCopyrightText;
			break;

		case 3:
			String	strTrackName = new String(abData);
			strMessage = "Sequence/Track Name: " +  strTrackName;
			break;

		case 4:
			String	strInstrumentName = new String(abData);
			strMessage = "Instrument Name: " + strInstrumentName;
			break;

		case 5:
			String	strLyrics = new String(abData);
			if (strLyrics.equals("\r\n"))
			    strLyrics = "\\n";
			strMessage = "Lyric: " + strLyrics;
			break;

		case 6:
			String	strMarkerText = new String(abData);
			strMessage = "Marker: " + strMarkerText;
			break;

		case 7:
			String	strCuePointText = new String(abData);
			strMessage = "Cue Point: " + strCuePointText;
			break;

		case 0x20:
			int	nChannelPrefix = abData[0] & 0xFF;
			strMessage = "MIDI Channel Prefix: " + nChannelPrefix;
			break;

		case 0x2F:
			strMessage = "End of Track";
			break;

		case 0x51:
			int	nTempo = ((abData[0] & 0xFF) << 16)
					| ((abData[1] & 0xFF) << 8)
					| (abData[2] & 0xFF);           // tempo in microseconds per beat
			float bpm = convertTempo(nTempo);
			// truncate it to 2 digits after dot
			bpm = (float) (Math.round(bpm*100.0f)/100.0f);
			strMessage = "Set Tempo: "+bpm+" bpm";
			break;

		case 0x54:
			// System.out.println("data array length: " + abData.length);
			strMessage = "SMTPE Offset: "
				+ (abData[0] & 0xFF) + ":"
				+ (abData[1] & 0xFF) + ":"
				+ (abData[2] & 0xFF) + "."
				+ (abData[3] & 0xFF) + "."
				+ (abData[4] & 0xFF);
			break;

		case 0x58:
			strMessage = "Time Signature: "
				+ (abData[0] & 0xFF) + "/" + (1 << (abData[1] & 0xFF))
				+ ", MIDI clocks per metronome tick: " + (abData[2] & 0xFF)
				+ ", 1/32 per 24 MIDI clocks: " + (abData[3] & 0xFF);
			break;

		case 0x59:
			String	strGender = (abData[1] == 1) ? "minor" : "major";
			strMessage = "Key Signature: " + sm_astrKeySignatures[abData[0] + 7] + " " + strGender;
			break;

		case 0x7F:
			// TODO: decode vendor code, dump data in rows
			String	strDataDump = getHexString(abData);
			strMessage = "Sequencer-Specific Meta event: " + strDataDump;
			break;

		default:
			String	strUnknownDump = getHexString(abData);
			strMessage = "unknown Meta event: " + strUnknownDump;
			break;

		}
		return strMessage;
	}



	public static String getKeyName(int nKeyNumber)
	{
		if (nKeyNumber > 127)
		{
			return "illegal value";
		}
		else
		{
			int	nNote = nKeyNumber % 12;
			int	nOctave = nKeyNumber / 12;
			return sm_astrKeyNames[nNote] + (nOctave - 1);
		}
	}


	public static int get14bitValue(int nLowerPart, int nHigherPart)
	{
		return (nLowerPart & 0x7F) | ((nHigherPart & 0x7F) << 7);
	}



	private static int signedByteToUnsigned(byte b)
	{
		return b & 0xFF;
	}

	// convert from microseconds per quarter note to beats per minute and vice versa
	private static float convertTempo(float value) {
		if (value <= 0) {
			value = 0.1f;
		}
		return 60000000.0f / value;
	}



	private static char hexDigits[] = 
	   {'0', '1', '2', '3', 
	    '4', '5', '6', '7', 
	    '8', '9', 'A', 'B', 
	    'C', 'D', 'E', 'F'};

	public static String getHexString(byte[] aByte)
	{
		StringBuffer	sbuf = new StringBuffer(aByte.length * 3 + 2);
		for (int i = 0; i < aByte.length; i++)
		{
			sbuf.append(' ');
			sbuf.append(hexDigits[(aByte[i] & 0xF0) >> 4]);
			sbuf.append(hexDigits[aByte[i] & 0x0F]);
			/*byte	bhigh = (byte) ((aByte[i] &  0xf0) >> 4);
			sbuf.append((char) (bhigh > 9 ? bhigh + 'A' - 10: bhigh + '0'));
			byte	blow = (byte) (aByte[i] & 0x0f);
			sbuf.append((char) (blow > 9 ? blow + 'A' - 10: blow + '0'));*/
		}
		return new String(sbuf);
	}
	
	private static String intToHex(int i) {
		return ""+hexDigits[(i & 0xF0) >> 4]
		         +hexDigits[i & 0x0F];
	}

	public static String getHexString(ShortMessage sm)
	{
		// bug in J2SDK 1.4.1
		// return getHexString(sm.getMessage());
		int status = sm.getStatus();
		String res = intToHex(sm.getStatus());
		// if one-byte message, return
		switch (status) {
			case 0xF6:			// Tune Request
			case 0xF7:			// EOX
	    		// System real-time messages
			case 0xF8:			// Timing Clock
			case 0xF9:			// Undefined
			case 0xFA:			// Start
			case 0xFB:			// Continue
			case 0xFC:			// Stop
			case 0xFD:			// Undefined
			case 0xFE:			// Active Sensing
			case 0xFF: return res;
		}
		res += ' '+intToHex(sm.getData1());
		// if 2-byte message, return
		switch (status) {
			case 0xF1:			// MTC Quarter Frame
			case 0xF3:			// Song Select
					return res;
		}
		switch (sm.getCommand()) {
			case 0xC0:
			case 0xD0:
					return res;
		}
		// 3-byte messages left
		res += ' '+intToHex(sm.getData2());
		return res;
	}
}



/*** DumpReceiver.java ***/


      
    

There are several sites with legal free MIDI files. The file Amy Winehouse - Rehab gives the result

	
---------------------------------------------------------------------------
File: rehab.mid
---------------------------------------------------------------------------
Length: 251475 ticks
Duration: 216788738 microseconds
---------------------------------------------------------------------------
DivisionType: PPQ
Resolution: 480 ticks per beat
---------------------------------------------------------------------------
Track 0:
-----------------------
tick 0: Time Signature: 4/4, MIDI clocks per metronome tick: 24, 1/32 per 24 MIDI clocks: 8
tick 0: Key Signature: C major
tick 0: SMTPE Offset: 32:0:0.0.0
tick 0: Set Tempo: 145.0 bpm
tick 0: End of Track
---------------------------------------------------------------------------
Track 1:
-----------------------
tick 0: Sequence/Track Name: amy winehouse - rehab
tick 0: Instrument Name: GM Device
tick 40: Sysex message: F0 7E 7F 09 01 F7
tick 40: End of Track
---------------------------------------------------------------------------
Track 2:
-----------------------
tick 0: MIDI Channel Prefix: 1
tick 0: Sequence/Track Name: amy winehouse - rehab
tick 0: Instrument Name: GM Device  2
tick 480: [B1 79 00] channel 2: control change 121 value: 0
tick 485: [B1 0A 40] channel 2: control change 10 value: 64
tick 490: [B1 5D 14] channel 2: control change 93 value: 20
tick 495: [B1 5B 00] channel 2: control change 91 value: 0
tick 500: [B1 0B 7F] channel 2: control change 11 value: 127
tick 505: [B1 07 69] channel 2: control change 7 value: 105
tick 510: [E1 00 40] channel 2: pitch wheel change 8192
tick 515: [B1 00 00] channel 2: control change 0 value: 0
tick 520: [C1 22] channel 2: program change 34
...
	
      

Playing a MIDI file

To play a MIDI file, you create a Sequence from a File, using the MidiSystem. You also create a Sequencer from the MidiSystem and pass it the sequence. The sequencer will output MIDI messages through its Transmitter. This completes setup of the MIDI event generation side of the system.

The play side is constructed by getting a Synthesizer from the MidiSystem. The Receiver is found from the synthesizer and is given to the transmitter of MIDI events. Play commences by calling start() on the sequencer, which reads from the file and passes MIDI events to its transmitter. These are passed to the synthesizer's receiver and played. The UML class diagram for the relevant classes is

This code is from Playing an audio file (easy)

      
      /*
 *	SimpleMidiPlayer.java
 *
 *	This file is part of jsresources.org
 */

/*
 * Copyright (c) 1999 - 2001 by Matthias Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

import java.io.File;
import java.io.IOException;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.ShortMessage;




/**	<titleabbrev>SimpleMidiPlayer</titleabbrev>
	<title>Playing a MIDI file (easy)</title>

	<formalpara><title>Purpose</title>
	<para>Plays a single MIDI file.</para></formalpara>

	<formalpara><title>Usage</title>
	<para>
	<cmdsynopsis>
	<command>java SimpleMidiPlayer</command>
	<arg choice="plain"><replaceable>midifile</replaceable></arg>
	</cmdsynopsis>
	</para></formalpara>

	<formalpara><title>Parameters</title>
	<variablelist>
	<varlistentry>
	<term><option><replaceable>midifile</replaceable></option></term>
	<listitem><para>the name of the MIDI file that should be
	played</para></listitem>
	</varlistentry>
	</variablelist>
	</formalpara>

	<formalpara><title>Bugs, limitations</title>

	<para>This program always uses the default Sequencer and the default
	Synthesizer to play on. For using non-default sequencers,
	synthesizers or to play on an external MIDI port, see
	<olink targetdoc="MidiPlayer"
	targetptr="MidiPlayer">MidiPlayer</olink>.</para>
	</formalpara>

	<formalpara><title>Source code</title>
	<para>
	<ulink url="SimpleMidiPlayer.java.html">SimpleMidiPlayer.java</ulink>
	</para>
	</formalpara>

*/
public class SimpleMidiPlayer
{
	/*
	  These variables are not really intended to be static in a
	  meaning of (good) design. They are used by inner classes, so they
	  can't just be automatic variables. There were three possibilities:

	  a) make them instance variables and instantiate the object they
	  belong to. This is clean (and is how you should do it in a real
	  application), but would have made the example more complex.

	  b) make them automatic final variables inside main(). Design-wise,
	  this is better than static, but automatic final is something that
	  is still like some black magic to me.

	  c) make them static variables, as it is done here. This is quite bad
	  design, because if you have global variables, you can't easily do
	  the thing they are used for two times in concurrency without risking
	  indeterministic behaviour. However, it makes the example easy to
	  read.
	 */
	private static Sequencer	sm_sequencer = null;
	private static Synthesizer	sm_synthesizer = null;



	public static void main(String[] args)
	{
		/*
		 *	We check if there is no command-line argument at all
		 *	or the first one is '-h'.
		 *	If so, we display the usage message and
		 *	exit.
		 */
		if (args.length == 0 || args[0].equals("-h"))
		{
			printUsageAndExit();
		}

		String	strFilename = args[0];
		File	midiFile = new File(strFilename);

		/*
		 *	We read in the MIDI file to a Sequence object.
		 *	This object is set at the Sequencer later.
		 */
		Sequence	sequence = null;
		try
		{
			sequence = MidiSystem.getSequence(midiFile);
		}
		catch (InvalidMidiDataException e)
		{
			/*
			 *	In case of an exception, we dump the exception
			 *	including the stack trace to the console.
			 *	Then, we exit the program.
			 */
			e.printStackTrace();
			System.exit(1);
		}
		catch (IOException e)
		{
			/*
			 *	In case of an exception, we dump the exception
			 *	including the stack trace to the console.
			 *	Then, we exit the program.
			 */
			e.printStackTrace();
			System.exit(1);
		}

		/*
		 *	Now, we need a Sequencer to play the sequence.
		 *	Here, we simply request the default sequencer.
		 *      With an argument of false, it does not create
		 *      a default syntesizer. With an argument of true
		 *      it is already linked to the default synthesizer
		 */
		try
		{
			sm_sequencer = MidiSystem.getSequencer(false);
		}
		catch (MidiUnavailableException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		if (sm_sequencer == null)
		{
			out("SimpleMidiPlayer.main(): can't get a Sequencer");
			System.exit(1);
		}

		/*
		 *	There is a bug in the Sun jdk1.3/1.4.
		 *	It prevents correct termination of the VM.
		 *	So we have to exit ourselves.
		 *	To accomplish this, we register a Listener to the Sequencer.
		 *	It is called when there are "meta" events. Meta event
		 *	47 is end of track.
		 *
		 *	Thanks to Espen Riskedal for finding this trick.
		 */
		sm_sequencer.addMetaEventListener(new MetaEventListener()
			{
				public void meta(MetaMessage event)
				{
					if (event.getType() == 47)
					{
						sm_sequencer.close();
						if (sm_synthesizer != null)
						{
							sm_synthesizer.close();
						}
						System.exit(0);
					}
				}
			});

		/*
		 *	The Sequencer is still a dead object.
		 *	We have to open() it to become live.
		 *	This is necessary to allocate some ressources in
		 *	the native part.
		 */
		try
		{
			sm_sequencer.open();
		}
		catch (MidiUnavailableException e)
		{
			e.printStackTrace();
			System.exit(1);
		}

		/*
		 *	Next step is to tell the Sequencer which
		 *	Sequence it has to play. In this case, we
		 *	set it as the Sequence object created above.
		 */
		try
		{
			sm_sequencer.setSequence(sequence);
		}
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}

		/*
		 *	Now, we set up the destinations the Sequence should be
		 *	played on. Here, we try to use the default
		 *	synthesizer. With some Java Sound implementations
		 *	(Sun jdk1.3/1.4 and others derived from this codebase),
		 *	the default sequencer and the default synthesizer
		 *	are combined in one object. We test for this
		 *	condition, and if it's true, nothing more has to
		 *	be done. With other implementations (namely Tritonus),
		 *	sequencers and synthesizers are always seperate
		 *	objects. In this case, we have to set up a link
		 *	between the two objects manually.
		 *
		 *	By the way, you should never rely on sequencers
		 *	being synthesizers, too; this is a highly non-
		 *	portable programming style. You should be able to
		 *	rely on the other case working. Alas, it is only
		 *	partly true for the Sun jdk1.3/1.4.
		 */
		Receiver	synthReceiver = null;
		if (! (sm_sequencer instanceof Synthesizer))
		{
			/*
			 *	We try to get the default synthesizer, open()
			 *	it and chain it to the sequencer with a
			 *	Transmitter-Receiver pair.
			 */
			try
			{
				sm_synthesizer = MidiSystem.getSynthesizer();
				sm_synthesizer.open();
					synthReceiver = sm_synthesizer.getReceiver();
				Transmitter	seqTransmitter = sm_sequencer.getTransmitter();
				seqTransmitter.setReceiver(synthReceiver);
			}
			catch (MidiUnavailableException e)
			{
				e.printStackTrace();
			}
		}

		/*
		 *	Now, we can start over.
		 */
		sm_sequencer.start();

	try {
	    Thread.sleep(5000);
	} catch (InterruptedException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}
	MidiChannel[] channels = sm_synthesizer.getChannels(); 
	System.out.println("Num channels is " + channels.length);
	for (int i = 0; i < channels.length; i++) {
	    channels[i].controlChange(7, 1);
	}

	//	Receiver synthReceiver = null;
	try {
	    synthReceiver = MidiSystem.getReceiver();
	} catch (Exception e) {
	}
	ShortMessage volMessage = new ShortMessage();
	int midiVolume = 100;
	for (Receiver rec: sm_synthesizer.getReceivers()) {
	    System.out.println("Setting vol on recveiver " + rec.toString());
	for (int i = 0; i < channels.length; i++) {
	    try {
		// volMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 123, midiVolume);
		volMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, midiVolume);
	    } catch (InvalidMidiDataException e) {}
	    synthReceiver.send(volMessage, -1);
	    rec.send(volMessage, -1);
	}
	}


	}



	private static void printUsageAndExit()
	{
		out("SimpleMidiPlayer: usage:");
		out("\tjava SimpleMidiPlayer <midifile>");
		System.exit(1);
	}



	private static void out(String strMessage)
	{
		System.out.println(strMessage);
	}
}



/*** SimpleMidiPlayer.java ***/

      
    

Playing a file to an external MIDI synthesizer

I have an Edirol Studio Canvas SD-20 synthesizer which I bought for a few hundred Australian dollars. This plugs into a PC through a USB port. Alsa recognises this by

	
 $ amidi -l
Dir Device    Name
IO  hw:2,0,0  SD-20 Part A
IO  hw:2,0,1  SD-20 Part B
I   hw:2,0,2  SD-20 MIDI
	
      

The list of MidiDevice.Info device information lists hw:2,0,0 twice, once for input and once for output, and similarly for the other values. The device information can be identified by the toString method, which returns values such as "SD20 [hw:2,0,0]". From the device information the device can be found as before using MidiSystem.getMidiDevice(info). The input and output devices can be distinguished by the number of maxOutputReceivers it supports: zero means none, while any other value (including minus one!) means it has a MIDI receiver. Selecting an external receiver is done by code like

	
		Receiver	synthReceiver = null;
		MidiDevice.Info[] devices;
		devices = MidiSystem.getMidiDeviceInfo();
		
		for (MidiDevice.Info info: devices) {
		    System.out.println("    Name: " + info.toString() + 
				       ", Decription: " +
				       info.getDescription() + 
				       ", Vendor: " +
				       info.getVendor());
		    if (info.toString().equals("SD20 [hw:2,0,0]")) {
			MidiDevice device = MidiSystem.getMidiDevice(info);
			if (device.getMaxReceivers() != 0) {
			    try {
				device.open();
				System.out.println("  max receivers: " + device.getMaxReceivers());
				receiver = device.getReceiver();
				System.out.println("Found a receiver");
				break;
			    } catch(Exception e) {}
			}
		    }
		}
	
      

Playing an audio file to my SD-20 is done by

      
      /*
 *	ExternalMidiPlayer.java
 *
 *	This file adapted from SimpleMidiPlayer of jsresources.org
 */

/*
 * Copyright (c) 1999 - 2001 by Matthias Pfisterer
 * Copyright (c) 2015 Jan Newmarch
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

import java.io.File;
import java.io.IOException;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiDeviceReceiver;



/**	<titleabbrev>SimpleMidiPlayer</titleabbrev>
	<title>Playing a MIDI file (easy)</title>

	<formalpara><title>Purpose</title>
	<para>Plays a single MIDI file.</para></formalpara>

	<formalpara><title>Usage</title>
	<para>
	<cmdsynopsis>
	<command>java SimpleMidiPlayer</command>
	<arg choice="plain"><replaceable>midifile</replaceable></arg>
	</cmdsynopsis>
	</para></formalpara>

	<formalpara><title>Parameters</title>
	<variablelist>
	<varlistentry>
	<term><option><replaceable>midifile</replaceable></option></term>
	<listitem><para>the name of the MIDI file that should be
	played</para></listitem>
	</varlistentry>
	</variablelist>
	</formalpara>

	<formalpara><title>Bugs, limitations</title>

	<para>This program always uses the default Sequencer and the default
	Synthesizer to play on. For using non-default sequencers,
	synthesizers or to play on an external MIDI port, see
	<olink targetdoc="MidiPlayer"
	targetptr="MidiPlayer">MidiPlayer</olink>.</para>
	</formalpara>

	<formalpara><title>Source code</title>
	<para>
	<ulink url="SimpleMidiPlayer.java.html">SimpleMidiPlayer.java</ulink>
	</para>
	</formalpara>

*/
public class ExternalMidiPlayer
{
	/*
	  These variables are not really intended to be static in a
	  meaning of (good) design. They are used by inner classes, so they
	  can't just be automatic variables. There were three possibilities:

	  a) make them instance variables and instantiate the object they
	  belong to. This is clean (and is how you should do it in a real
	  application), but would have made the example more complex.

	  b) make them automatic final variables inside main(). Design-wise,
	  this is better than static, but automatic final is something that
	  is still like some black magic to me.

	  c) make them static variables, as it is done here. This is quite bad
	  design, because if you have global variables, you can't easily do
	  the thing they are used for two times in concurrency without risking
	  indeterministic behaviour. However, it makes the example easy to
	  read.
	 */
	private static Sequencer	sm_sequencer = null;
	private static Synthesizer	sm_synthesizer = null;
        private static Receiver receiver = null;


	public static void main(String[] args) throws MidiUnavailableException
	{
		/*
		 *	We check if there is no command-line argument at all
		 *	or the first one is '-h'.
		 *	If so, we display the usage message and
		 *	exit.
		 */
		if (args.length == 0 || args[0].equals("-h"))
		{
			printUsageAndExit();
		}

		String	strFilename = args[0];
		File	midiFile = new File(strFilename);

		/*
		 *	We read in the MIDI file to a Sequence object.
		 *	This object is set at the Sequencer later.
		 */
		Sequence	sequence = null;
		try
		{
			sequence = MidiSystem.getSequence(midiFile);
		}
		catch (InvalidMidiDataException e)
		{
			/*
			 *	In case of an exception, we dump the exception
			 *	including the stack trace to the console.
			 *	Then, we exit the program.
			 */
			e.printStackTrace();
			System.exit(1);
		}
		catch (IOException e)
		{
			/*
			 *	In case of an exception, we dump the exception
			 *	including the stack trace to the console.
			 *	Then, we exit the program.
			 */
			e.printStackTrace();
			System.exit(1);
		}

		/*
		 *	Now, we need a Sequencer to play the sequence.
		 *	Here, we simply request the default sequencer.
		 *      With an argument of false, it does not create
		 *      a default syntesizer. With an argument of true
		 *      it is already linked to the default synthesizer
		 */
		try
		{
			sm_sequencer = MidiSystem.getSequencer(false);
		}
		catch (MidiUnavailableException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		if (sm_sequencer == null)
		{
			out("SimpleMidiPlayer.main(): can't get a Sequencer");
			System.exit(1);
		}

		/*
		 *	There is a bug in the Sun jdk1.3/1.4.
		 *	It prevents correct termination of the VM.
		 *	So we have to exit ourselves.
		 *	To accomplish this, we register a Listener to the Sequencer.
		 *	It is called when there are "meta" events. Meta event
		 *	47 is end of track.
		 *
		 *	Thanks to Espen Riskedal for finding this trick.
		 */
		sm_sequencer.addMetaEventListener(new MetaEventListener()
			{
				public void meta(MetaMessage event)
				{
					if (event.getType() == 47)
					{
						sm_sequencer.close();
						if (sm_synthesizer != null)
						{
							sm_synthesizer.close();
						}
						System.exit(0);
					}
				}
			});

		/*
		 *	The Sequencer is still a dead object.
		 *	We have to open() it to become live.
		 *	This is necessary to allocate some ressources in
		 *	the native part.
		 */
		/*
		try
		{
		    sm_sequencer.open();
		}
		catch (MidiUnavailableException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		*/
		/*
		 *	Next step is to tell the Sequencer which
		 *	Sequence it has to play. In this case, we
		 *	set it as the Sequence object created above.
		 */
		try
		{
			sm_sequencer.setSequence(sequence);
		}
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}

		/*
		 *	Now, we set up the destinations the Sequence should be
		 *	played on.
		 */
		Receiver	synthReceiver = null;
		MidiDevice.Info[] devices;
		devices = MidiSystem.getMidiDeviceInfo();
		
		for (MidiDevice.Info info: devices) {
		    System.out.println("    Name: " + info.toString() + 
				       ", Decription: " +
				       info.getDescription() + 
				       ", Vendor: " +
				       info.getVendor());
		    if (info.toString().equals("SD20 [hw:2,0,0]")) {
			MidiDevice device = MidiSystem.getMidiDevice(info);
			if (device.getMaxReceivers() != 0) {
			    try {
				device.open();
				System.out.println("  max receivers: " + device.getMaxReceivers());
				receiver = device.getReceiver();
				System.out.println("Found a receiver");
				break;
			    } catch(Exception e) {}
			}
		    }
		}
		
		if (receiver == null) {
		    System.out.println("Reeciver is null");
		    System.exit(1);
		}
		try
		    {
			Transmitter	seqTransmitter = sm_sequencer.getTransmitter();
			seqTransmitter.setReceiver(receiver);
		    }
		catch (MidiUnavailableException e)
		    {
			e.printStackTrace();
		    }

		/*
		 *	Now, we can start over.
		 */
		sm_sequencer.open();
		sm_sequencer.start();

		try {
		    Thread.sleep(5000);
		} catch (InterruptedException e) {
		    // TODO Auto-generated catch block
		    e.printStackTrace();
		}
		
	}



	private static void printUsageAndExit()
	{
		out("SimpleMidiPlayer: usage:");
		out("\tjava SimpleMidiPlayer <midifile>");
		System.exit(1);
	}



	private static void out(String strMessage)
	{
		System.out.println(strMessage);
	}
}



/*** SimpleMidiPlayer.java ***/

      
    

Changing the soundbank

The soundbank is a set of "sounds" encoded in some way that are used to generate the music played. The default sound synthesizer for Java is the Gervill synthesizer, and this looks for its default soundbank in $HOME/.gervill/soundbank-emg.sf2. This default soundbank is tiny, only 1.9MBytes in size, and sounds, well, poor quality.

DaWicked1 in Better Java-midi instrument sounds for Linux offers two methods to improve this: the simpler is to replace the soundfont with a better one such as the Fluidsynth font, using the default name.

The second method is programmatic and probably better as it allows more flexibility and choice at runtime.

Changing pitch and speed

Changing the speed of playback of a MIDI file means changing the rate that MIDI messages are sent from the sequencer. The Java sequencers have methods to control this such as setTempoFactor. The sequencer will respond to this method by sending the messages at a different rate.

Changing the pitch of the notes can be done by altering the pitch of the NOTE_ON and NOTE_OFF messages. This has to be done not just for future notes, but also for notes currently playing. Fortunately there is a MIDI command Pitch Bend which can be sent to a synthesizer to change the pitch of all currently playing and future notes. A pitch bend value of 0x2000 corresponds to no pitch change, while values upto 0x4000 are increases in pitch, below are decreases in pitch. There are many sites giving complex formulae for this, but the simplest seems to be MIDI Pitch Bend Range which states that a change of pitch by 683 is roughly a semitone. So we change the pitch value and send a new Pitch Bend event to the receiver.

We look for input from the user of ←, ↑, →, ↓ (ESC-[A, etc). These then call the appropriate method. The program illustrating this is an adaptation of the SimpleMidiPlayer given earlier in the chapter and is AdjustableMidiPlayer.java:

      
      /*
 *	SimpleMidiPlayer.java
 *
 *	This file is part of jsresources.org
 */

/*
 *      AdjustableMidiPlayer.java
 *      can change the speed and pitch of the MIDI player
 *      Copyright (c) 2016 Jan Newmarch
 */

/*
 * Copyright (c) 1999 - 2001 by Matthias Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
  |<---            this code is formatted to fit into 80 columns             --->|
*/

import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.MidiMessage;

public class AdjustableMidiPlayer
{
    private Sequencer	sm_sequencer = null;
    private Synthesizer	sm_synthesizer = null;
    private Receiver	synthReceiver = null;

    private int pitch = 0x2000;

    public static void main(String[] args)
    {

	if (args.length == 0 || args[0].equals("-h"))
	    {
		printUsageAndExit();
	    }


	String	strFilename = args[0];
	File	midiFile = new File(strFilename);

	AdjustableMidiPlayer amp = new AdjustableMidiPlayer();
	amp.play(midiFile);
    }

    public void play(File midiFile) {
	Sequence	sequence = null;
	try
	    {
		sequence = MidiSystem.getSequence(midiFile);
	    }
	catch (InvalidMidiDataException e)
	    {
		/*
		 *	In case of an exception, we dump the exception
		 *	including the stack trace to the console.
		 *	Then, we exit the program.
		 */
		e.printStackTrace();
		System.exit(1);
	    }
	catch (IOException e)
	    {

		e.printStackTrace();
		System.exit(1);
	    }

	try
	    {
		sm_sequencer = MidiSystem.getSequencer(false);
	    }
	catch (MidiUnavailableException e)
	    {
		e.printStackTrace();
		System.exit(1);
	    }

	if (sm_sequencer == null)
	    {
		out("SimpleMidiPlayer.main(): can't get a Sequencer");
		System.exit(1);
	    }

	sm_sequencer.addMetaEventListener(new MetaEventListener()
	    {
		public void meta(MetaMessage event)
		{
		    if (event.getType() == 47)
			{
			    sm_sequencer.close();
			    if (sm_synthesizer != null)
				{
				    sm_synthesizer.close();
				}
			    System.exit(0);
			}
		}
	    });

	try
	    {
		sm_sequencer.open();
	    }
	catch (MidiUnavailableException e)
	    {
		e.printStackTrace();
		System.exit(1);
	    }

	try
	    {
		sm_sequencer.setSequence(sequence);
	    }
	catch (InvalidMidiDataException e)
	    {
		e.printStackTrace();
		System.exit(1);
	    }


	if (! (sm_sequencer instanceof Synthesizer))
	    {

		try
		    {
			sm_synthesizer = MidiSystem.getSynthesizer();
			sm_synthesizer.open();
			synthReceiver = sm_synthesizer.getReceiver();
			Transmitter	seqTransmitter = sm_sequencer.getTransmitter();
			seqTransmitter.setReceiver(synthReceiver);
		    }
		catch (MidiUnavailableException e)
		    {
			e.printStackTrace();
		    }
	    }


	sm_sequencer.start();

	/*
	try {
	    Thread.sleep(5000);
	} catch (InterruptedException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}
	*/

	MidiChannel[] channels = sm_synthesizer.getChannels(); 
	System.out.println("Num channels is " + channels.length);
	for (int i = 0; i < channels.length; i++) {
	    channels[i].controlChange(7, 1);
	}

	//	Receiver synthReceiver = null;
	try {
	    synthReceiver = MidiSystem.getReceiver();
	} catch (Exception e) {
	}
	ShortMessage volMessage = new ShortMessage();
	int midiVolume = 100;
	for (Receiver rec: sm_synthesizer.getReceivers()) {
	    System.out.println("Setting vol on recveiver " + rec.toString());
	    for (int i = 0; i < channels.length; i++) {
		try {
		    // volMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 123, midiVolume);
		    volMessage.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, midiVolume);
		} catch (InvalidMidiDataException e) {}
		synthReceiver.send(volMessage, -1);
		rec.send(volMessage, -1);
	    }
	}


	BufferedReader br = new BufferedReader(new
					       InputStreamReader(System.in));
	String str = null;
	System.out.println("Enter lines of text.");
	System.out.println("Enter 'stop' to quit.");
	do {
	    try {
		str = br.readLine();
		if (str.length() >= 2) {
		    byte[] bytes = str.getBytes();
		    if (bytes[0] == 27 && bytes[1] == 91) {
			if (bytes[2] == 65) {
			    // up
			    increasePitch();
			} else if (bytes[2] == 66) {
			    // down
			    decreasePitch();
			} else if (bytes[2] == 67) {
			    //right
			    faster();
			} else if (bytes[2] == 68) {
			    //left
			    slower();
			}
		    }
		}
	    } catch(java.io.IOException e) {
	    }
	} while(!str.equals("stop")); 
    }

    private void increasePitch() {
	// 683 from http://www.ultimatemetal.com/forum/threads/midi-pitch-bend-range.680677/
	pitch += 683;
	for (int n = 0; n < 16; n++) {
	    try {
		MidiMessage msg = 
		    new ShortMessage(ShortMessage.PITCH_BEND,
				     n,
				     pitch & 0x7F, pitch >> 7);
		synthReceiver.send(msg, 0);
	    } catch (Exception e) {
	    }
	}
    }

    private void decreasePitch() {
	// 683 from http://www.ultimatemetal.com/forum/threads/midi-pitch-bend-range.680677/
	pitch -= 683;
	for (int n = 0; n < 16; n++) {
	    try {
		MidiMessage msg = 
		    new ShortMessage(ShortMessage.PITCH_BEND,
				     n,
				     pitch & 0x7F, pitch >> 7);
		synthReceiver.send(msg, 0);
	    } catch (Exception e) {
	    }
	}
    }

    float speed = 1.0f;

    private void faster() {
	speed *= 1.2f;
	sm_sequencer.setTempoFactor(speed);
    }

    private void slower() {
	speed /= 1.2f;
	sm_sequencer.setTempoFactor(speed);
    }

    private static void printUsageAndExit()
    {
	out("SimpleMidiPlayer: usage:");
	out("\tjava SimpleMidiPlayer <midifile>");
	System.exit(1);
    }



    private static void out(String strMessage)
    {
	System.out.println(strMessage);
    }


}


/*** AdjustableMidiPlayer.java ***/

      
    

Using TiMidity instead of the default Gervill synthesizer

The softsynth TiMidity can be run as a backend synthesizer using the Alsa sequencer by

      
$timidity -iA -B2,8 -Os -EFreverb=0

Opening sequencer port: 128:0 128:1 128:2 128:3
      
    

(and similarly for Fluidsynth.). This is opened on ports 128:0 etc.

Unfortunately this is not directly visible to JavaSound which expects either the default Gervill synthesizer or a raw MIDI synthesizer such as a hardware synthesizer. As discussed in the MIDI Alsa chapter, we can fix this by using Alsa raw MIDI ports.

We add raw MIDI ports by

      
modprobe snd-seq snd-virmidi
      
    

This will bring virtual devices both into the ALSA raw MIDI and into the ALSA sequencer spaces:

      
$amidi -l
Dir Device    Name
IO  hw:3,0    Virtual Raw MIDI (16 subdevices)
IO  hw:3,1    Virtual Raw MIDI (16 subdevices)
IO  hw:3,2    Virtual Raw MIDI (16 subdevices)
IO  hw:3,3    Virtual Raw MIDI (16 subdevices)

$aplaymidi -l
 Port    Client name                      Port name
 14:0    Midi Through                     Midi Through Port-0
 28:0    Virtual Raw MIDI 3-0             VirMIDI 3-0
 29:0    Virtual Raw MIDI 3-1             VirMIDI 3-1
 30:0    Virtual Raw MIDI 3-2             VirMIDI 3-2
 31:0    Virtual Raw MIDI 3-3             VirMIDI 3-3
      
    

Virtual Raw MIDI port 3-0 can then be connected to TiMidity port 0 by

      
aconnect 28:0 128:0
      
    

The final step in playing to TiMidity is to change one line of AdaptableMidiPlayer.java from

      
if (info.toString().equals("SD20 [hw:2,0,0]")) {
      
    

to

      
if (info.toString().equals("VirMIDI [hw:3,0,0]")) {
      
    

Conclusion

This chapter has built a number of programs using the MIDI API and discussed how to use external hardware synthesizers and soft sythesizers such as TiMidity.


Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
"Programming and Using Linux Sound - in depth" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/LinuxSound/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book