Java tutorial
/* * Copyright (C) 2012 Klaus Reimer <k@ailis.de> * See LICENSE.txt for licensing information. */ package de.ailis.midi4js; import java.applet.Applet; import java.util.HashMap; import java.util.Map; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MidiDevice; import javax.sound.midi.MidiDevice.Info; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.Receiver; import javax.sound.midi.Transmitter; import netscape.javascript.JSObject; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; /** * Applet which exposes access to MIDI hardware to JavaScript. * * @author Klaus Reimer (k@ailis.de) */ public class Midi4JS extends Applet { /** Serial version UID. */ private static final long serialVersionUID = 1L; /** Map from device handle to device. */ private transient Map<Integer, MidiDevice> deviceMap; /** Map from receiver handle to receiver. */ private transient Map<Integer, Receiver> receiverMap; /** Map from transmitter handle to transmitter. */ private transient Map<Integer, Transmitter> transmitterMap; /** * Returns the JavaScript namespace of Midi4JS. * * @return The JavaScript namespace. */ JSObject getJSNamespace() { try { final JSObject window = JSObject.getWindow(this); final JSObject namespace = (JSObject) window.getMember("midi4js"); return namespace; } catch (final Exception e) { System.err.println("Unable to get midi4js JavaScript " + "namespace. Maybe JavaScript context is already destroyed? " + "Original exception: "); e.printStackTrace(System.err); return null; } } /** * Executes a method on the JavaScript namespace of Midi4JS. * * @param methodName * The name of the method to execute. * @param arguments * Optional arguments. */ void execJSMethod(final String methodName, final Object... arguments) { // Get the JavaScript midi4js namespace. Do nothing if not found. final JSObject namespace = getJSNamespace(); if (namespace == null) return; try { namespace.call(methodName, arguments); } catch (final Exception e) { System.err.println( "Unable to call method '" + methodName + "' " + "on midi4js namespace. Original exception: "); e.printStackTrace(System.err); } } /** * @see java.applet.Applet#start() */ @Override public void start() { System.out.println("Started midi4js applet (Instance #" + System.identityHashCode(this) + ")"); this.deviceMap = new HashMap<Integer, MidiDevice>(); this.receiverMap = new HashMap<Integer, Receiver>(); this.transmitterMap = new HashMap<Integer, Transmitter>(); execJSMethod("appletStarted"); } /** * @see java.applet.Applet#stop() */ @Override public void stop() { for (final Receiver receiver : this.receiverMap.values()) receiver.close(); for (final Transmitter transmitter : this.transmitterMap.values()) transmitter.close(); for (final MidiDevice device : this.deviceMap.values()) if (device.isOpen()) device.close(); this.deviceMap = null; this.receiverMap = null; this.transmitterMap = null; System.out.println("Stopped midi4js applet (Instance #" + System.identityHashCode(this) + ")"); } /** * Resolves a device handle into a MIDI device. If this fails then an * exception is thrown. * * @param handle * The device handle. * @return The MIDI device. */ private MidiDevice resolveDeviceHandle(final int handle) { final MidiDevice device = this.deviceMap.get(handle); if (device == null) throw new RuntimeException("No MIDI device with handle " + handle + " found"); return device; } /** * Resolves a receiver handle into a receiver. If this fails then an * exception is thrown. * * @param handle * The receiver handle. * @return The receiver. */ private Receiver resolveReceiverHandle(final int handle) { final Receiver receiver = this.receiverMap.get(handle); if (receiver == null) throw new RuntimeException("No receiver with handle " + handle + " found"); return receiver; } /** * Resolves a transmitter handle into a transmitter. If this fails then an * exception is thrown. * * @param handle * The transmitter handle. * @return The transmitter. */ private Transmitter resolveTransmitterHandle(final int handle) { final Transmitter transmitter = this.transmitterMap.get(handle); if (transmitter == null) throw new RuntimeException("No transmitter with handle " + handle + " found"); return transmitter; } /** * Writes MIDI device info into the specified JSON stringer. * * @param json * The JSON stringer. * @param info * The MIDI device info. * @throws JSONException * When JSON output fails. */ private void deviceInfo(final JSONStringer json, final Info info) throws JSONException { json.object(); json.key("name").value(info.getName()); json.key("description").value(info.getDescription()); json.key("vendor").value(info.getVendor()); json.key("version").value(info.getVersion()); json.endObject(); } /** * Returns an array of information objects representing the set of all MIDI * devices available on the system. * * @return The array of information objects about all available MIDI * devices. * @throws JSONException * When JSON string could not be constructed. */ public String getMidiDeviceInfo() throws JSONException { final JSONStringer js = new JSONStringer(); js.array(); for (final Info info : MidiSystem.getMidiDeviceInfo()) { js.object(); js.key("name").value(info.getName()); js.key("description").value(info.getDescription()); js.key("vendor").value(info.getVendor()); js.key("version").value(info.getVersion()); js.endObject(); } js.endArray(); return js.toString(); } /** * Returns the handle for the midi device with the specified index. The * device must be released by calling the releaseMidiDevice() method! * * @param index * The device index. * @return The device handle. * @throws MidiUnavailableException * If the midi device is not available. */ public int getMidiDevice(final int index) throws MidiUnavailableException { final Info[] infos = MidiSystem.getMidiDeviceInfo(); final MidiDevice device = MidiSystem.getMidiDevice(infos[index]); final int handle = System.identityHashCode(device); this.deviceMap.put(handle, device); return handle; } /** * Releases the specified device handle. * * @param deviceHandle * The device handle to release. */ public void releaseMidiDevice(final int deviceHandle) { this.deviceMap.remove(deviceHandle); } /** * Checks how many transmitters the specified device supports. -1 means * unlimited number of transmitters. * * @param deviceHandle * The device handle. * @return The maximum number of transmitters. -1 for unlimited. */ public int getMaxTransmitters(final int deviceHandle) { return resolveDeviceHandle(deviceHandle).getMaxTransmitters(); } /** * Checks how many receivers the specified device supports. -1 means * unlimited number of receivers. * * @param deviceHandle * The device handle. * @return The maximum number of receivers. -1 for unlimited. */ public int getMaxReceivers(final int deviceHandle) { return resolveDeviceHandle(deviceHandle).getMaxReceivers(); } /** * Returns MIDI IN receiver for the specified device. A receiver must * be closed when no longer needed. * * @param deviceHandle * The handle of the MIDI device. * @return The handle of the receiver. * @throws MidiUnavailableException * When MIDI device is unavailable. */ public int getReceiver(final int deviceHandle) throws MidiUnavailableException { final MidiDevice device = resolveDeviceHandle(deviceHandle); final Receiver receiver = device.getReceiver(); final int receiverHandle = System.identityHashCode(receiver); this.receiverMap.put(receiverHandle, receiver); return receiverHandle; } /** * Releases the specifeid receiver handle. * * @param receiverHandle * The receiver handle to release. */ public void closeReceiver(final int receiverHandle) { final Receiver receiver = resolveReceiverHandle(receiverHandle); receiver.close(); this.receiverMap.remove(receiverHandle); } /** * Returns all currently open receivers. * * @param deviceHandle * The device handle. * @return All currently open receivers in form of a JSON-encoded string * which describes an array of receiver handles. * @throws JSONException * When JSON data could not be constructed. */ public String getReceivers(final int deviceHandle) throws JSONException { final MidiDevice device = resolveDeviceHandle(deviceHandle); final JSONStringer json = new JSONStringer(); json.array(); for (final Receiver receiver : device.getReceivers()) { json.value(System.identityHashCode(receiver)); } json.endArray(); return json.toString(); } /** * Returns MIDI IN transmitter for the specified device. A transmitter must * be closed when no longer needed. * * @param deviceHandle * The handle of the MIDI device. * @return The handle of the transmitter. * @throws MidiUnavailableException * When MIDI device is unavailable. */ public int getTransmitter(final int deviceHandle) throws MidiUnavailableException { final MidiDevice device = resolveDeviceHandle(deviceHandle); final Transmitter transmitter = device.getTransmitter(); final int transmitterHandle = System.identityHashCode(transmitter); this.transmitterMap.put(transmitterHandle, transmitter); return transmitterHandle; } /** * Releases the specifeid transmitter handle. * * @param transmitterHandle * The transmitter handle to release. */ public void closeTransmitter(final int transmitterHandle) { final Transmitter transmitter = resolveTransmitterHandle(transmitterHandle); transmitter.close(); this.transmitterMap.remove(transmitterHandle); } /** * Returns all currently open transmitters. * * @param deviceHandle * The device handle. * @return All currently open transmitters in form of a JSON-encoded string * which describes an array of transmitter handles. * @throws JSONException * When JSON data could not be constructed. */ public String getTransmitters(final int deviceHandle) throws JSONException { final MidiDevice device = resolveDeviceHandle(deviceHandle); final JSONStringer json = new JSONStringer(); json.array(); for (final Transmitter transmitter : device.getTransmitters()) { json.value(System.identityHashCode(transmitter)); } json.endArray(); return json.toString(); } /** * Sends a MIDI message to a receiver. * * @param receiverHandle * The handle of the receiver. * @param jsonMessageStr * Then message encoded as a JSON string * @param timeStamp * The message timestamp * @throws InvalidMidiDataException * When the midi data is invalid. * @throws JSONException * When JSON data could not be parsed. */ public void sendMessage(final int receiverHandle, final String jsonMessageStr, final long timeStamp) throws InvalidMidiDataException, JSONException { final Receiver receiver = resolveReceiverHandle(receiverHandle); final JSONObject json = new JSONObject(jsonMessageStr); final JSONArray jsonData = json.getJSONArray("data"); final int length = jsonData.length(); final byte[] data = new byte[length]; for (int i = 0; i < length; i++) data[i] = (byte) (jsonData.getInt(i) & 0xff); final RawMidiMessage message = new RawMidiMessage(data); receiver.send(message, timeStamp); } /** * Sets the receiver of a transmitter. * * @param transmitterHandle * The handle of the transmitter. * @param receiverHandle * The handle of the receiver. 0 to unset. */ public void setTransmitterReceiver(final int transmitterHandle, final int receiverHandle) { final Transmitter transmitter = resolveTransmitterHandle(transmitterHandle); final Receiver receiver = receiverHandle == 0 ? null : resolveReceiverHandle(receiverHandle); transmitter.setReceiver(receiver); } /** * Returns the handle of the receiver which is connected with the * specified transmitter. * * @param transmitterHandle * The handle of the transmitter. * @return The handle of the receiver or 0 if none. */ public int getTransmitterReceiver(final int transmitterHandle) { final Transmitter transmitter = resolveTransmitterHandle(transmitterHandle); final Receiver receiver = transmitter.getReceiver(); return System.identityHashCode(receiver); } /** * Creates a new receiver and returns the handle on it. * * @return The handle for the receiver. */ public int createReceiver() { final Receiver receiver = new MessageReceiver(this); final int handle = System.identityHashCode(receiver); this.receiverMap.put(handle, receiver); return handle; } /** * Opens the specified MIDI device. * * @param deviceHandle * The device handle. * @throws MidiUnavailableException * When MIDI device is not available. */ public void openMidiDevice(final int deviceHandle) throws MidiUnavailableException { final MidiDevice device = resolveDeviceHandle(deviceHandle); device.open(); } /** * Checks if the specified MIDI device is open. * * @param deviceHandle * The device handle. * @return True if device is, open false if not. */ public boolean isMidiDeviceOpen(final int deviceHandle) { final MidiDevice device = resolveDeviceHandle(deviceHandle); return device.isOpen(); } /** * Closes the specified MIDI device. * * @param deviceHandle * The device handle. * @throws MidiUnavailableException * When MIDI device is not available. */ public void closeMidiDevice(final int deviceHandle) throws MidiUnavailableException { final MidiDevice device = resolveDeviceHandle(deviceHandle); device.close(); } /** * Returns the device info of the specified device. * * @param deviceHandle * The device handle. * @return The device info as a JSON string. * @throws JSONException * When JSON output fails. */ public String getMidiDeviceInfo(final int deviceHandle) throws JSONException { final MidiDevice device = resolveDeviceHandle(deviceHandle); final JSONStringer json = new JSONStringer(); deviceInfo(json, device.getDeviceInfo()); return json.toString(); } /** * Returns the current timestamp of the device. * * @param deviceHandle * The device handle. * @return The current timestamp of the device. */ public long getMidiDeviceMicrosecondPosition(final int deviceHandle) { final MidiDevice device = resolveDeviceHandle(deviceHandle); return device.getMicrosecondPosition(); } }