de.ailis.midi4js.Midi4JS.java Source code

Java tutorial

Introduction

Here is the source code for de.ailis.midi4js.Midi4JS.java

Source

/*
 * 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();
    }
}