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

/*
 * Copyright (c) 1999 - 2006 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 javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.SysexMessage;


// TODO: an optional delay parameter that is added to getMicrosecondPosition to be used as timestamp for the event delivery.

/**	<titleabbrev>MidiNote</titleabbrev>
	<title>Playing a note on a MIDI device</title>

	<formalpara><title>Purpose</title>
	<para>Plays a single note on a MIDI device. The MIDI device can
	be a software synthesizer, an internal hardware synthesizer or
	any device connected to the MIDI OUT port.</para>
	</formalpara>

	<formalpara><title>Usage</title>
	<para>
	<cmdsynopsis><command>java MidiNote</command>
	<arg choice="opt"><replaceable class="parameter">devicename</replaceable></arg>
	<arg choice="plain"><replaceable class="parameter">keynumber</replaceable></arg>
	<arg choice="plain"><replaceable class="parameter">velocity</replaceable></arg>
	<arg choice="plain"><replaceable class="parameter">duration</replaceable></arg>
	</cmdsynopsis>
	</para></formalpara>

	<formalpara><title>Parameters</title>
	<variablelist>
	<varlistentry>
	<term><replaceable class="parameter">devicename</replaceable></term>
	<listitem><para>the name of the device to send the MIDI messages to</para></listitem>
	</varlistentry>
	<varlistentry>
	<term><replaceable class="parameter">keynumber</replaceable></term>
	<listitem><para>the MIDI key number</para></listitem>
	</varlistentry>
	<varlistentry>
	<term><replaceable class="parameter">velocity</replaceable></term>
	<listitem><para>the velocity</para></listitem>
	</varlistentry>
	<varlistentry>
	<term><replaceable class="parameter">duration</replaceable></term>
	<listitem><para>the duration in milliseconds</para></listitem>
	</varlistentry>
	</variablelist>
	</formalpara>

	<formalpara><title>Bugs, limitations</title>
	<para>Not well-tested.</para>
	</formalpara>

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

*/
public class MidiNote
{
	/**	Flag for debugging messages.
	 	If true, some messages are dumped to the console
	 	during operation.
	*/
	private static boolean		DEBUG = true;



	public static void main(String[] args)
	{
		// TODO: make settable via command line
		int	nChannel = 0;

		int	nKey = 0;	// MIDI key number
		int	nVelocity = 0;

		/*
		 *	Time between note on and note off event in
		 *	milliseconds. Note that on most systems, the
		 *	best resolution you can expect are 10 ms.
		 */
		int	nDuration = 0;
		int	nArgumentIndexOffset = 0;
		String	strDeviceName = null;
		if (args.length == 4)
		{
			strDeviceName = args[0];
			nArgumentIndexOffset = 1;
		}
		else if (args.length == 3)
		{
			nArgumentIndexOffset = 0;
		}
		else
		{
			printUsageAndExit();
		}
		nKey = Integer.parseInt(args[0 + nArgumentIndexOffset]);
		nKey = Math.min(127, Math.max(0, nKey));
		nVelocity = Integer.parseInt(args[1 + nArgumentIndexOffset]);
		nVelocity = Math.min(127, Math.max(0, nVelocity));
		nDuration = Integer.parseInt(args[2 + nArgumentIndexOffset]);
		nDuration = Math.max(0, nDuration);


		MidiDevice	outputDevice = null;
		Receiver	receiver = null;
		if (strDeviceName != null)
		{
			MidiDevice.Info	info = MidiCommon.getMidiDeviceInfo(strDeviceName, true);
			if (info == null)
			{
				out("no device info found for name " + strDeviceName);
				System.exit(1);
			}
			try
			{
				outputDevice = MidiSystem.getMidiDevice(info);
				if (DEBUG) out("MidiDevice: " + outputDevice);
				outputDevice.open();
			}
			catch (MidiUnavailableException e)
			{
				if (DEBUG) out(e);
			}
			if (outputDevice == null)
			{
				out("wasn't able to retrieve MidiDevice");
				System.exit(1);
			}
			try
			{
				receiver = outputDevice.getReceiver();
			}
			catch (MidiUnavailableException e)
			{
				if (DEBUG) out(e);
			}
		}
		else
		{
			/*	We retrieve a Receiver for the default
				MidiDevice.
			*/
			try
			{
				receiver = MidiSystem.getReceiver();
			}
			catch (MidiUnavailableException e)
			{
				if (DEBUG) { out(e); }
			}
		}
		if (receiver == null)
		{
			out("wasn't able to retrieve Receiver");
			System.exit(1);
		}

		if (DEBUG) out("Receiver: " + receiver);
		/*	Here, we prepare the MIDI messages to send.
			Obviously, one is for turning the key on and
			one for turning it off.
		*/
		ShortMessage	onMessage = null;
		ShortMessage	offMessage = null;
		try
		{
			onMessage = new ShortMessage();
			offMessage = new ShortMessage();
			onMessage.setMessage(ShortMessage.NOTE_ON, nChannel, nKey, nVelocity);
			offMessage.setMessage(ShortMessage.NOTE_OFF, nChannel, nKey, 0);

			if (DEBUG)
			    {
			    out("On Msg: " + onMessage.getStatus() + " " + onMessage.getData1() + " " + onMessage.getData2());
			    out("Off Msg: " + offMessage.getStatus() + " " + offMessage.getData1() + " " + offMessage.getData2());
			}
		}
		catch (InvalidMidiDataException e)
		{
			if (DEBUG) { out(e); }
		}

		/*
		 *	Turn the note on
		 */
		if (DEBUG) out("sending on message...");
		receiver.send(onMessage, -1);
		if (DEBUG) out("...sent");

		/*
		 *	Wait for the specified amount of time
		 *	(the duration of the note).
		 */
		try
		{
			Thread.sleep(nDuration);
		}
		catch (InterruptedException e)
		{
			if (DEBUG) out(e);
		}

		/*
		 *	Turn the note off.
		 */
		if (DEBUG) out("sending off message...");
		receiver.send(offMessage, -1);
		if (DEBUG) out("...sent");

		/*
		 *	Clean up.
		 */
		receiver.close();
		if (outputDevice != null)
		{
			outputDevice.close();
		}
	}



	private static void printUsageAndExit()
	{
		out("MidiNote: usage:");
		out("  java MidiNote [<device name>] <note number> <velocity> <duration>");
		out("    <device name>\toutput to named device");
		System.exit(1);
	}



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



	private static void out(Throwable t)
	{
		t.printStackTrace();
	}
}



/*** MidiNote.java ***/