org.monome.pages.configuration.MonomeConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.monome.pages.configuration.MonomeConfiguration.java

Source

/*
 *  MonomeConfiguration.java
 * 
 *  Copyright (c) 2010, Tom Dinchak
 * 
 *  This file is part of Pages.
 *
 *  Pages is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Pages is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with Pages; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

package org.monome.pages.configuration;

import java.io.IOException;
import java.util.ArrayList;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Transmitter;

import org.apache.commons.lang.StringEscapeUtils;

import org.monome.pages.Main;
import org.monome.pages.gui.MonomeDisplayFrame;
import org.monome.pages.gui.MonomeFrame;

import org.monome.pages.midi.MidiDeviceFactory;
import org.monome.pages.pages.Page;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.illposed.osc.OSCMessage;
import com.illposed.osc.OSCPortOut;

/**
 * @author Administrator
 *
 */
public class MonomeConfiguration extends OSCDeviceConfiguration<Page> {

    /**
    * The monome's width (ie. 8 or 16)
    */
    public int sizeX;

    /**
     * The monome's height (ie. 8 or 16) 
     */
    public int sizeY;

    /**
    * ledState[x][y] - The LED state cache for the monome
    */
    public int[][] ledState = new int[32][32];

    /**
     * pageState[page_num][x][y] - The LED state cache for each page
     */
    public int[][][] pageState = new int[255][32][32];

    /**
    * The monome's pattern banks
    */
    public ArrayList<PatternBank> patternBanks = new ArrayList<PatternBank>();

    /**
    * The options dropdown when creating a new page (contains a list of all page names)
    */
    private String options[];

    /**
     * 1 when the page change button is held down (bottom right button) 
     */
    private int pageChangeMode = 0;

    /**
     * true if a page has been changed while the page change button was held down 
     */
    private boolean pageChanged = false;

    public int[] pageChangeDelays = new int[255];

    /**
     * The current MIDI clock tick number (resets every measure, 1/96 resolution)
     */
    private int tickNum = 0;

    /**
     * true if the page change button should be active
     */
    public boolean usePageChangeButton = true;

    /**
     * true if MIDI page changing should be active
     */
    public boolean useMIDIPageChanging = false;

    /**
     * The array of devices that MIDI page change messages can come from
     */
    String[] pageChangeMidiInDevices = new String[32];

    public int offsetX;

    public int offsetY;

    public boolean altClear = false;

    public int serialOSCPort;

    public transient OSCPortOut serialOSCPortOut;

    public String serialOSCHostname;

    public transient MonomeOSCListener oscListener;

    private MonomeFrame monomeFrame;

    public ArrayList<Press> pressesInPlayback = new ArrayList<Press>();

    /**
    * @param index the index to assign to this MonomeConfiguration
    * @param prefix the prefix of the monome (/40h)
    * @param serial the serial # of the monome (if auto-discovery was used)
    * @param sizeX the width of the monome in buttons (ie. 8)
    * @param sizeY the height of the monome in buttons (ie. 8)
    * @param usePageChangeButton if true enable the MIDI page change button
    * @param useMIDIPageChanging if true use MIDI page changing rules
    * @param midiPageChangeRules the set of MIDI page changing rules
    * @param monomeFrame the GUI frame for this monome
    */
    public MonomeConfiguration(int index, String prefix, String serial, int sizeX, int sizeY,
            boolean usePageChangeButton, boolean useMIDIPageChanging,
            ArrayList<MIDIPageChangeRule> midiPageChangeRules, MonomeFrame monomeFrame) {
        super(index, prefix, serial);

        this.options = PagesRepository.getPageNames(Page.class);
        for (int i = 0; i < options.length; i++) {
            options[i] = options[i].substring(17);
        }
        this.sizeX = sizeX;
        this.sizeY = sizeY;

        this.midiPageChangeRules = midiPageChangeRules;
        this.usePageChangeButton = usePageChangeButton;
        this.useMIDIPageChanging = useMIDIPageChanging;
        this.deviceFrame = monomeFrame;
        this.monomeFrame = monomeFrame;

        if (monomeFrame != null) {
            monomeFrame.updateMidiInMenuOptions(MidiDeviceFactory.getMidiInOptions());
            monomeFrame.updateMidiOutMenuOptions(MidiDeviceFactory.getMidiOutOptions());
        }

        this.clearMonome();
    }

    @Override
    protected void onPageAdd(String className, Page page) {
        int numPatterns = this.sizeX;
        this.patternBanks.add(this.numPages, new PatternBank(numPatterns, page));

        System.out.println("MonomeConfiguration " + this.serial + ": created " + className + " page");
    }

    @Override
    protected Class getPageType() {
        return Page.class;
    }

    /**
    * Destroys this object.
    *
    */
    public void destroy() {
        for (int i = 0; i < this.numPages; i++) {
            deletePage(i);
        }
        MonomeConfigurationFactory.removeMonomeConfiguration(index);
    }

    /**
    * Redraws only the Ableton pages (for when a new event arrives)
    */
    public void redrawAbletonPages() {
        if (this.pages.size() == 0) {
            return;
        }
        for (int i = 0; i < this.pages.size(); i++) {
            pages.get(i).handleAbletonEvent();
        }
    }

    /**
     * Handles a press event from the monome.
     * 
     * @param x The x coordinate of the button pressed.
     * @param y The y coordinate of the button pressed.
     * @param value The type of event (1 = press, 0 = release)
     */
    public synchronized void handlePress(int x, int y, int value) {
        if (deviceFrame != null) {
            MonomeDisplayFrame monomeDisplayFrame = monomeFrame.getMonomeDisplayFrame();
            if (monomeDisplayFrame != null) {
                monomeDisplayFrame.press(x, y, value);
            }
        }

        // if we have no pages then dont handle any button presses
        if (this.pages.size() == 0) {
            return;
        }

        // if the monome isn't configured to handle this button then don't handle it
        // ie if you config a 256 as a 64 and hit a button out of range
        if (y >= this.sizeY || x >= this.sizeX) {
            return;
        }

        // stop here if we don't want to use the page change button
        if (this.usePageChangeButton == false) {
            // pass presses to the current page and record them in the pattern bank
            if (this.pages.get(curPage) != null) {
                this.patternBanks.get(curPage).recordPress(x, y, value, curPage);
                this.pages.get(curPage).handlePress(x, y, value);
            }
            return;
        }

        // if page change mode is on and this is a button on the bottom row then change page and return
        if (this.pageChangeMode == 1) {
            // if this is the bottom right button and we let go turn it off
            // and send the value == 1 press along to the page
            if (x == (this.sizeX - 1) && y == (this.sizeY - 1) && value == 0) {
                this.clear(0, -1);
                this.pageChangeMode = 0;
                if (this.pageChanged == false) {
                    if (this.pages.get(curPage) != null) {
                        this.pages.get(curPage).handlePress(x, y, 1);
                        this.patternBanks.get(curPage).recordPress(x, y, 1, curPage);
                        this.pages.get(curPage).handlePress(x, y, 0);
                        this.patternBanks.get(curPage).recordPress(x, y, 0, curPage);
                    }
                }
                if (this.pages.get(curPage) != null) {
                    this.ledState = new int[32][32];
                    this.pages.get(curPage).redrawDevice();
                }
                return;
            }
            int nextPage = x + ((this.sizeY - y - 1) * this.sizeX);
            int patternNum = x;
            int numPages = this.pages.size();
            if (numPages > this.sizeX - 1) {
                numPages++;
            }
            if (value == 1) {
                if (numPages > nextPage && nextPage < (this.sizeX * this.sizeY) / 2) {
                    // offset back by one because of the page change button
                    if (nextPage > this.sizeX - 1) {
                        nextPage--;
                    }
                    this.curPage = nextPage;
                    this.switchPage(this.pages.get(this.curPage), this.curPage, true);
                } else if (y == 0 && value == 1) {
                    this.pages.get(curPage).onBlur();
                    this.patternBanks.get(this.curPage).handlePress(patternNum);
                }
                this.pageChanged = true;
            }
            return;
        }

        // if this is the bottom right button and we pressed the button (value == 1), turn page change mode on
        if (x == (this.sizeX - 1) && y == (this.sizeY - 1) && value == 1) {
            this.pageChangeMode = 1;
            this.pageChanged = false;
            if (this.pageChangeDelays[this.curPage] > 0) {
                new Thread(new PageChangeTimer(this, this.pageChangeDelays[this.curPage])).start();
            }
            this.clear(0, -1);
            this.drawPatternState();
            this.pages.get(curPage).onBlur();
            return;
        }

        // pass presses to the current page and record them in the pattern bank
        if (this.pages.get(curPage) != null) {
            this.patternBanks.get(curPage).recordPress(x, y, value, curPage);
            this.pages.get(curPage).handlePress(x, y, value);
        }
    }

    @Override
    public void dispose() {
        if (monomeFrame != null && monomeFrame.monomeDisplayFrame != null) {
            monomeFrame.monomeDisplayFrame.dispose();
        }

        super.dispose();
    }

    class PageChangeTimer implements Runnable {

        MonomeConfiguration monome;
        int delay;

        public PageChangeTimer(MonomeConfiguration monome, int delay) {
            this.monome = monome;
            this.delay = delay;
        }

        public void run() {
            try {
                Thread.sleep(delay);
                this.monome.pageChanged = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Draws the pattern state on the monome when the page change key is held down.
     * Blinking = triggered, solid = recorded, dark = empty.
     */
    public void drawPatternState() {
        for (int x = 0; x < this.sizeX; x++) {
            if (this.patternBanks.get(curPage).getPatternState(x) == PatternBank.PATTERN_STATE_TRIGGERED) {
                if (this.ledState[x][0] == 1) {
                    this.led(x, 0, 0, -1);
                } else {
                    this.led(x, 0, 1, -1);
                }
            } else if (this.patternBanks.get(curPage).getPatternState(x) == PatternBank.PATTERN_STATE_RECORDED) {
                this.led(x, 0, 1, -1);
            } else if (this.patternBanks.get(curPage).getPatternState(x) == PatternBank.PATTERN_STATE_EMPTY) {
                this.led(x, 0, 0, -1);
            }
        }

        for (int x = 0; x < this.sizeX; x++) {
            for (int y = 0; y < this.sizeY; y++) {
                int pageNum = x + (y * this.sizeX);
                // offset by one for the page change button
                if (pageNum == this.sizeX - 1) {
                    continue;
                }
                if (pageNum > this.sizeX - 1) {
                    pageNum--;
                }
                if (pageNum < this.numPages) {
                    int ledX = x;
                    int ledY = this.sizeY - y - 1;
                    if (pageNum == curPage) {
                        if (this.ledState[ledX][ledY] == 1) {
                            this.led(ledX, ledY, 0, -1);
                        } else {
                            this.led(ledX, ledY, 1, -1);
                        }
                    } else {
                        this.led(ledX, ledY, 1, -1);
                    }
                }
            }
        }
    }

    /**
     * Called every time a MIDI clock sync 'tick' is received, this triggers each page's handleTick() method
     */
    public synchronized void tick(MidiDevice device) {
        for (int i = 0; i < this.numPages; i++) {
            for (int j = 0; j < this.midiInDevices[i].length; j++) {
                if (this.midiInDevices[i][j] == null) {
                    continue;
                }
                if (this.midiInDevices[i][j].compareTo(device.getDeviceInfo().getName()) == 0) {
                    ArrayList<Press> presses = patternBanks.get(i).getRecordedPresses();
                    if (presses != null) {
                        for (int k = 0; k < presses.size(); k++) {
                            int[] press = presses.get(k).getPress();
                            for (int pb = 0; pb < this.pressesInPlayback.size(); pb++) {
                                if (pressesInPlayback.get(pb) == null)
                                    continue;
                                int[] pbPress = pressesInPlayback.get(pb).getPress();
                                if (press[0] == pbPress[0] && press[1] == pbPress[1] && press[2] == 0) {
                                    pressesInPlayback.remove(pb);
                                }
                            }
                            if (press[2] == 1) {
                                pressesInPlayback.add(presses.get(k));
                            }
                            this.pages.get(i).handleRecordedPress(press[0], press[1], press[2],
                                    presses.get(k).getPatternNum());
                        }
                    }
                    this.patternBanks.get(i).handleTick();
                    this.pages.get(i).handleTick(device);
                }
            }
        }
        if (this.pageChangeMode == 1 && this.tickNum % 12 == 0) {
            this.drawPatternState();
        }
        this.tickNum++;
        if (this.tickNum == 96) {
            this.tickNum = 0;
        }
    }

    /**
     * Called every time a MIDI clock sync 'reset' is received, this triggers each page's handleReset() method.
     */
    public void reset(MidiDevice device) {
        for (int i = 0; i < this.numPages; i++) {
            for (int j = 0; j < this.midiInDevices[i].length; j++) {
                if (this.midiInDevices[i][j] == null) {
                    continue;
                }
                if (this.midiInDevices[i][j].compareTo(device.getDeviceInfo().getName()) == 0) {
                    this.pages.get(i).handleReset();
                    this.patternBanks.get(i).handleReset();
                }
            }
        }
        this.tickNum = 0;
    }

    /**
     * Called every time a MIDI message is received, the messages are passed along to each page.
     * 
     * @param message The MIDI message received
     * @param timeStamp The timestamp of the MIDI message
     */
    public synchronized void send(MidiDevice device, MidiMessage message, long timeStamp) {
        if (this.useMIDIPageChanging) {
            if (message instanceof ShortMessage) {
                ShortMessage msg = (ShortMessage) message;
                int velocity = msg.getData1();
                if (msg.getCommand() == ShortMessage.NOTE_ON && velocity > 0) {
                    int channel = msg.getChannel();
                    int note = msg.getData1();

                    for (int j = 0; j < this.pageChangeMidiInDevices.length; j++) {
                        if (this.pageChangeMidiInDevices[j] == null) {
                            continue;
                        }
                        if (this.pageChangeMidiInDevices[j].compareTo(device.getDeviceInfo().getName()) == 0) {
                            for (int i = 0; i < this.midiPageChangeRules.size(); i++) {
                                MIDIPageChangeRule mpcr = this.midiPageChangeRules.get(i);
                                if (mpcr.checkNoteRule(note, channel) == true) {
                                    int switchToPageIndex = mpcr.getPageIndex();
                                    Page page = this.pages.get(switchToPageIndex);
                                    this.switchPage(page, switchToPageIndex, true);
                                }
                            }
                        }
                    }
                }
                if (msg.getCommand() == ShortMessage.CONTROL_CHANGE) {
                    int cc = msg.getData1();
                    int ccVal = msg.getData2();
                    int channel = msg.getChannel();
                    for (int j = 0; j < this.pageChangeMidiInDevices.length; j++) {
                        if (this.pageChangeMidiInDevices[j] == null) {
                            continue;
                        }
                        if (this.pageChangeMidiInDevices[j].compareTo(device.getDeviceInfo().getName()) == 0) {
                            for (int i = 0; i < this.midiPageChangeRules.size(); i++) {
                                MIDIPageChangeRule mpcr = this.midiPageChangeRules.get(i);
                                if (mpcr.checkCCRule(cc, ccVal, channel) == true) {
                                    int switchToPageIndex = mpcr.getPageIndex();
                                    Page page = this.pages.get(switchToPageIndex);
                                    this.switchPage(page, switchToPageIndex, true);
                                }
                            }
                        }
                    }
                }
            }
        }
        for (int i = 0; i < this.numPages; i++) {
            for (int j = 0; j < this.midiInDevices[i].length; j++) {
                if (this.midiInDevices[i][j] == null) {
                    continue;
                }
                if (this.midiInDevices[i][j].compareTo(device.getDeviceInfo().getName()) == 0) {
                    this.pages.get(i).send(message, timeStamp);
                }
            }
        }
    }

    /**
     * Turns a MIDI In device on or off for the current page.
     * 
     * @param deviceName the MIDI device name
     */
    public void toggleMidiInDevice(String deviceName) {
        if (curPage < 0 || curPage > 254) {
            return;
        }
        for (int i = 0; i < this.midiInDevices[this.curPage].length; i++) {
            // if this device was enabled, disable it
            if (this.midiInDevices[this.curPage][i] == null) {
                continue;
            }
            if (this.midiInDevices[this.curPage][i].compareTo(deviceName) == 0) {
                midiInDevices[this.curPage][i] = new String();
                if (this.deviceFrame != null) {
                    this.deviceFrame.updateMidiInSelectedItems(midiInDevices[this.curPage]);
                }
                return;
            }
        }

        // if we didn't disable it, enable it
        for (int i = 0; i < this.midiInDevices[this.curPage].length; i++) {
            if (this.midiInDevices[this.curPage][i] == null) {
                this.midiInDevices[this.curPage][i] = deviceName;
                if (this.deviceFrame != null) {
                    this.deviceFrame.updateMidiInSelectedItems(midiInDevices[this.curPage]);
                }
                return;
            }
        }
    }

    /**
     * Toggles a MIDI In device as able to receive page change rules.
     * 
     * @param deviceName the name of the MIDI device
     */
    public void togglePageChangeMidiInDevice(String deviceName) {
        for (int i = 0; i < this.pageChangeMidiInDevices.length; i++) {
            // if this device was enabled, disable it
            if (this.pageChangeMidiInDevices[i] == null) {
                continue;
            }
            if (this.pageChangeMidiInDevices[i].compareTo(deviceName) == 0) {
                pageChangeMidiInDevices[i] = new String();
                this.deviceFrame.updatePageChangeMidiInSelectedItems(pageChangeMidiInDevices);
                return;
            }
        }

        // if we didn't disable it, enable it
        for (int i = 0; i < this.pageChangeMidiInDevices.length; i++) {
            if (this.pageChangeMidiInDevices[i] == null) {
                this.pageChangeMidiInDevices[i] = deviceName;
                if (this.deviceFrame != null) {
                    this.deviceFrame.updatePageChangeMidiInSelectedItems(pageChangeMidiInDevices);
                }
                return;
            }
        }
    }

    /**
     * Toggles a MIDI Out device on or off for the current page.
     * 
     * @param deviceName the name of the MIDI device
     */
    public void toggleMidiOutDevice(String deviceName) {
        if (curPage < 0 || curPage > 254) {
            return;
        }
        for (int i = 0; i < this.midiOutDevices[this.curPage].length; i++) {
            // if this device was enabled, disable it
            if (this.midiOutDevices[this.curPage][i] == null) {
                continue;
            }
            if (this.midiOutDevices[this.curPage][i].compareTo(deviceName) == 0) {
                midiOutDevices[this.curPage][i] = new String();
                if (this.deviceFrame != null) {
                    this.deviceFrame.updateMidiOutSelectedItems(midiOutDevices[this.curPage]);
                }
                return;
            }
        }

        // if we didn't disable it, enable it
        for (int i = 0; i < this.midiOutDevices[this.curPage].length; i++) {
            if (this.midiOutDevices[this.curPage][i] == null) {
                this.midiOutDevices[this.curPage][i] = deviceName;
                if (this.deviceFrame != null) {
                    this.deviceFrame.updateMidiOutSelectedItems(midiOutDevices[this.curPage]);
                }
                return;
            }
        }
    }

    /**
     * Sends a /led x y value command to the monome if index is the selected page.
     * 
     * @param x The x coordinate of the led
     * @param y The y coordinate of the led
     * @param value The value of the led (1 = on, 0 = off)
     * @param index The index of the page making the request
     */
    public void led(int x, int y, int value, int index) {
        if (x < 0 || y < 0 || value < 0 || x >= this.sizeX || y >= this.sizeY || value > 1) {
            return;
        }

        if (index > -1) {
            this.pageState[index][x][y] = value;

            if (index != this.curPage) {
                return;
            }

            if (this.pageChangeMode == 1) {
                return;
            }

            if (this.pages.size() <= index || this.pages.get(index) == null) {
                return;
            }

            if (this.pages.get(index) != null && this.pages.get(index).getCacheDisabled() == false) {
                if (this.ledState[x][y] == value) {
                    return;
                }
            }
        }

        this.ledState[x][y] = value;

        if (this.deviceFrame != null) {
            MonomeDisplayFrame monomeDisplayFrame = monomeFrame.getMonomeDisplayFrame();
            if (monomeDisplayFrame != null) {
                monomeDisplayFrame.setLedState(ledState);
            }
        }

        Object args[] = new Object[3];
        args[0] = new Integer(x);
        args[1] = new Integer(y);
        args[2] = new Integer(value);
        OSCMessage msg;
        try {
            if (this.serialOSCPort == 0) {
                msg = new OSCMessage(this.prefix + "/led", args);
                if (Main.main.configuration.monomeSerialOSCPortOut != null) {
                    Main.main.configuration.monomeSerialOSCPortOut.send(msg);
                }
            } else {
                msg = new OSCMessage(this.prefix + "/grid/led/set", args);
                serialOSCPortOut.send(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Clear the monome.
     */
    public void clearMonome() {
        this.clear(0, 0);
    }

    /**
     * Sends a led_col message to the monome if index is the selected page.
     * 
     * @param col The column to effect
     * @param value1 The first 8 bits of the value
     * @param value2 The second 8 bits of the value
     * @param index The index of the page making the call
     */
    public void led_col(ArrayList<Integer> intArgs, int index) {
        int col = intArgs.get(0);
        if (col < 0 || col >= sizeX) {
            return;
        }
        int[] values = { 0, 0, 0, 0 };
        int numValues = 0;
        for (int i = 0; i < intArgs.size(); i++) {
            if (i > 4) {
                break;
            }
            values[i] = intArgs.get(i);
            numValues++;
        }
        int fullvalue = (values[3] << 16) + (values[2] << 8) + values[1];
        if (index > -1) {
            for (int y = 0; y < (intArgs.size() - 1) * 8; y++) {
                if (y >= sizeY) {
                    break;
                }
                int bit = (fullvalue >> (this.sizeY - y - 1)) & 1;
                this.pageState[index][col][sizeY - y - 1] = bit;
            }

            if (index != this.curPage) {
                return;
            }

            if (this.pageChangeMode == 1) {
                return;
            }

            for (int y = 0; y < (intArgs.size() - 1) * 8; y++) {
                int bit = (fullvalue >> (this.sizeY - y - 1)) & 1;
                this.ledState[col][y] = bit;
            }

            if (this.deviceFrame != null) {
                MonomeDisplayFrame monomeDisplayFrame = monomeFrame.getMonomeDisplayFrame();
                if (monomeDisplayFrame != null) {
                    monomeDisplayFrame.setLedState(ledState);
                }
            }
        }

        Object args[] = new Object[numValues];
        args[0] = new Integer(col);
        for (int i = 1; i < numValues; i++) {
            args[i] = (Integer) intArgs.get(i);
        }
        OSCMessage msg;
        try {
            if (this.serialOSCPort == 0) {
                msg = new OSCMessage(this.prefix + "/led_col", args);
                if (Main.main.configuration.monomeSerialOSCPortOut != null) {
                    Main.main.configuration.monomeSerialOSCPortOut.send(msg);
                }
            } else {
                Object newArgs[] = new Object[numValues + 1];
                newArgs[0] = intArgs.get(0);
                newArgs[1] = new Integer(0);
                if (intArgs.size() > 1) {
                    newArgs[2] = intArgs.get(1);
                }
                if (intArgs.size() > 2) {
                    newArgs[3] = intArgs.get(2);
                }
                msg = new OSCMessage(this.prefix + "/grid/led/col", newArgs);
                serialOSCPortOut.send(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a led_row message to the monome if index is the selected page.
     * 
     * @param row The row to effect
     * @param value1 The first 8 bits of the value
     * @param value2 The second 8 bits of the value
     * @param index The index of the page making the call
     */
    public void led_row(ArrayList<Integer> intArgs, int index) {
        int row = intArgs.get(0);
        if (row < 0 || row >= sizeY) {
            return;
        }
        int[] values = { 0, 0, 0, 0 };
        int numValues = 0;
        for (int i = 0; i < intArgs.size(); i++) {
            if (i > 4) {
                break;
            }
            values[i] = intArgs.get(i);
            numValues++;
        }
        int fullvalue = (values[3] << 16) + (values[2] << 8) + values[1];
        if (index > -1) {
            for (int x = 0; x < (intArgs.size() - 1) * 8; x++) {
                if (x >= sizeX) {
                    break;
                }
                int bit = (fullvalue >> (this.sizeX - x - 1)) & 1;
                this.pageState[index][sizeX - x - 1][row] = bit;
            }

            if (index != this.curPage) {
                return;
            }

            if (this.pageChangeMode == 1) {
                return;
            }

            for (int x = 0; x < (intArgs.size() - 1) * 8; x++) {
                int bit = (fullvalue >> (this.sizeX - x - 1)) & 1;
                this.ledState[x][row] = bit;
            }

            if (this.deviceFrame != null) {
                MonomeDisplayFrame monomeDisplayFrame = monomeFrame.getMonomeDisplayFrame();
                if (monomeDisplayFrame != null) {
                    monomeDisplayFrame.setLedState(ledState);
                }
            }
        }

        Object args[] = new Object[numValues];
        args[0] = new Integer(row);
        for (int i = 0; i < numValues; i++) {
            args[i] = (Integer) intArgs.get(i);
        }
        OSCMessage msg;
        try {
            if (this.serialOSCPort == 0) {
                msg = new OSCMessage(this.prefix + "/led_row", args);
                if (Main.main.configuration.monomeSerialOSCPortOut != null) {
                    Main.main.configuration.monomeSerialOSCPortOut.send(msg);
                }
            } else {
                Object newArgs[] = new Object[numValues + 1];
                newArgs[0] = new Integer(0);
                newArgs[1] = intArgs.get(0);
                if (intArgs.size() > 1) {
                    newArgs[2] = intArgs.get(1);
                }
                if (intArgs.size() > 2) {
                    newArgs[3] = intArgs.get(2);
                }
                msg = new OSCMessage(this.prefix + "/grid/led/row", newArgs);
                if (serialOSCPortOut != null) {
                    serialOSCPortOut.send(msg);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a frame message to the monome if index is the selected page
     * TODO: implement this method
     * 
     * @param x 
     * @param y
     * @param values
     * @param index
     */
    public void frame(int x, int y, int[] values, int index) {
        for (int i = 0; i < values.length; i++) {
        }
    }

    /**
     * Sends a clear message to the monome if index is the selected page
     * 
     * @param state See monome OSC spec 
     * @param index The index of the page making the call
     */
    public synchronized void clear(int state, int index) {
        if (state == 0 || state == 1) {

            if (index > -1) {
                for (int x = 0; x < this.sizeX; x++) {
                    for (int y = 0; y < this.sizeY; y++) {
                        this.pageState[index][x][y] = state;
                    }
                }

                if (index != this.curPage) {
                    return;
                }

                if (this.pageChangeMode == 1) {
                    return;
                }

                for (int x = 0; x < this.sizeX; x++) {
                    for (int y = 0; y < this.sizeY; y++) {
                        this.ledState[x][y] = state;
                    }
                }

                if (this.deviceFrame != null) {
                    MonomeDisplayFrame monomeDisplayFrame = monomeFrame.getMonomeDisplayFrame();
                    if (monomeDisplayFrame != null) {
                        monomeDisplayFrame.setLedState(ledState);
                    }
                }
            }

            Object args[] = new Object[1];
            args[0] = new Integer(state);
            OSCMessage msg;
            try {
                if (this.serialOSCPort == 0) {
                    if (altClear) {
                        for (int y = 0; y < this.sizeY; y++) {
                            ArrayList<Integer> argz = new ArrayList<Integer>();
                            argz.add(y);
                            argz.add(0);
                            argz.add(0);
                            this.led_row(argz, index);
                        }
                    } else {
                        msg = new OSCMessage(this.prefix + "/clear", args);
                        Configuration configuration = Main.main.configuration;
                        if (configuration != null && configuration.monomeSerialOSCPortOut != null) {
                            configuration.monomeSerialOSCPortOut.send(msg);
                        }
                    }
                } else {
                    msg = new OSCMessage(this.prefix + "/grid/led/all", args);
                    if (serialOSCPortOut != null) {
                        serialOSCPortOut.send(msg);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Converts the current monome configuration to XML.
     * 
     * @return XML representing the current monome configuration
     */
    public String toXml() {
        String xml = "";
        xml += "  <monome>\n";
        xml += "    <prefix>" + this.prefix + "</prefix>\n";
        xml += "    <serial>" + this.serial + "</serial>\n";
        xml += "    <sizeX>" + this.sizeX + "</sizeX>\n";
        xml += "    <sizeY>" + this.sizeY + "</sizeY>\n";
        if (this.serialOSCHostname != null) {
            xml += "    <serialOSCHostname>" + this.serialOSCHostname + "</serialOSCHostname>\n";
        }
        String state = "off";
        if (altClear) {
            state = "on";
        }
        xml += "    <altClear>" + state + "</altClear>\n";
        xml += "    <usePageChangeButton>" + (this.usePageChangeButton ? "true" : "false")
                + "</usePageChangeButton>\n";
        xml += "    <useMIDIPageChanging>" + (this.useMIDIPageChanging ? "true" : "false")
                + "</useMIDIPageChanging>\n";
        for (int i = 0; i < this.pageChangeMidiInDevices.length; i++) {
            if (pageChangeMidiInDevices[i] == null || pageChangeMidiInDevices[i].compareTo("") == 0) {
                continue;
            }
            xml += "    <selectedpagechangemidiinport>" + StringEscapeUtils.escapeXml(pageChangeMidiInDevices[i])
                    + "</selectedpagechangemidiinport>\n";
        }
        for (int i = 0; i < this.midiPageChangeRules.size(); i++) {
            MIDIPageChangeRule mpcr = this.midiPageChangeRules.get(i);
            if (mpcr != null) {
                xml += "    <MIDIPageChangeRule>\n";
                xml += "      <pageIndex>" + mpcr.getPageIndex() + "</pageIndex>\n";
                xml += "      <note>" + mpcr.getNote() + "</note>\n";
                xml += "      <channel>" + mpcr.getChannel() + "</channel>\n";
                xml += "      <cc>" + mpcr.getCC() + "</cc>\n";
                xml += "      <ccVal>" + mpcr.getCCVal() + "</ccVal>\n";
                xml += "      <linkedSerial>" + mpcr.getLinkedSerial() + "</linkedSerial>\n";
                xml += "      <linkedPageIndex>" + mpcr.getLinkedPageIndex() + "</linkedPageIndex>\n";
                xml += "    </MIDIPageChangeRule>\n";
            }
        }

        for (int i = 0; i < this.numPages; i++) {
            if (this.pages.get(i).toXml() != null) {
                xml += "    <page class=\"" + this.pages.get(i).getClass().getName() + "\">\n";
                xml += this.pages.get(i).toXml();
                for (int j = 0; j < midiInDevices[i].length; j++) {
                    if (midiInDevices[i][j] == null || midiInDevices[i][j].compareTo("") == 0) {
                        continue;
                    }
                    xml += "      <selectedmidiinport>" + StringEscapeUtils.escapeXml(midiInDevices[i][j])
                            + "</selectedmidiinport>\n";
                }
                for (int j = 0; j < midiOutDevices[i].length; j++) {
                    if (midiOutDevices[i][j] == null || midiOutDevices[i][j].compareTo("") == 0) {
                        continue;
                    }
                    xml += "      <selectedmidioutport>" + StringEscapeUtils.escapeXml(midiOutDevices[i][j])
                            + "</selectedmidioutport>\n";
                }
                xml += "      <pageChangeDelay>" + this.pageChangeDelays[i] + "</pageChangeDelay>\n";
                int patternLength = this.patternBanks.get(i).getPatternLength();
                int quantization = this.patternBanks.get(i).getQuantization();
                xml += "      <patternlength>" + patternLength + "</patternlength>\n";
                xml += "      <quantization>" + quantization + "</quantization>\n";
                xml += "    </page>\n";
            }
        }
        xml += "  </monome>\n";
        return xml;
    }

    public void setPatternLength(int pageNum, int length) {
        if (this.patternBanks.size() <= pageNum) {
            this.patternBanks.add(pageNum, new PatternBank(this.sizeX, this.pages.get(pageNum)));
        }
        this.patternBanks.get(pageNum).setPatternLength(length);
    }

    public void setQuantization(int pageNum, int quantization) {
        if (this.patternBanks.size() <= pageNum) {
            this.patternBanks.add(pageNum, new PatternBank(this.sizeX, this.pages.get(pageNum)));
        }
        this.patternBanks.get(pageNum).setQuantization(quantization);
    }

    /**
     * @return The MIDI outputs that have been enabled in the main configuration.
     */
    public String[] getMidiOutOptions(int index) {
        return this.midiOutDevices[index];
    }

    /**
     * The Receiver object for the MIDI device named midiDeviceName. 
     * 
     * @param midiDeviceName The name of the MIDI device to get the Receiver for
     * @return The MIDI receiver
     */
    public Receiver getMidiReceiver(String midiDeviceName) {
        return MidiDeviceFactory.getMIDIReceiverByName(midiDeviceName);
    }

    /**
     * The Transmitter object for the MIDI device named midiDeviceName.
     * 
     * @param midiDeviceName The name of the MIDI device to get the Transmitter for
     * @return The MIDI transmitter
     */
    public Transmitter getMidiTransmitter(String midiDeviceName) {
        return MidiDeviceFactory.getMIDITransmitterByName(midiDeviceName);
    }

    /**
     * Used to clean up OSC connections held by individual pages.
     */
    public void destroyPage() {
        for (int i = 0; i < this.numPages; i++) {
            this.pages.get(i).destroyPage();
        }
    }

    /**
     * Sets the title bar of this MonomeConfiguration's MonomeFrame
     */
    public void setFrameTitle() {
        String title = "";
        if (prefix != null) {
            title += prefix;
        }
        if (serial != null) {
            title += " | " + serial;
        }
        if (sizeX != 0 && sizeY != 0) {
            title += " | " + sizeX + "x" + sizeY;
        }
        if (this.deviceFrame != null) {
            deviceFrame.setTitle(title);
        }
    }

    public void sendMidi(ShortMessage midiMsg, int index) {
        String[] midiOutOptions = getMidiOutOptions(index);
        for (int i = 0; i < midiOutOptions.length; i++) {
            if (midiOutOptions[i] == null) {
                continue;
            }
            Receiver recv = getMidiReceiver(midiOutOptions[i]);
            if (recv != null) {
                recv.send(midiMsg, MidiDeviceFactory.getDevice(recv).getMicrosecondPosition());
            }
        }
    }

    public void reload() {
        ledState = new int[32][32];
        pageState = new int[255][32][32];
        for (Page page : pages) {
            page.redrawDevice();
        }
    }

    public void initMonome() {
        class InitMonomeAnimation implements Runnable {

            MonomeConfiguration monomeConfig;

            public InitMonomeAnimation(MonomeConfiguration monomeConfig) {
                this.monomeConfig = monomeConfig;
            }

            public void run() {
                for (int value = 1; value >= 0; value--) {
                    int[][] leds = new int[monomeConfig.sizeX][monomeConfig.sizeY];
                    for (int led = 0; led < monomeConfig.sizeX * monomeConfig.sizeY; led++) {
                        boolean found = false;
                        int x = 0;
                        int y = 0;
                        while (!found) {
                            x = (int) (Math.random() * monomeConfig.sizeX);
                            y = (int) (Math.random() * monomeConfig.sizeY);
                            if (leds[x][y] == 0) {
                                found = true;
                                leds[x][y] = 1;
                            }
                        }
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        led(x, y, value, -1);
                    }
                }
                if (monomeConfig.pages.size() > curPage)
                    monomeConfig.pages.get(curPage).redrawDevice();
            }
        }

        new Thread(new InitMonomeAnimation(this)).start();
    }

    public void handleTilt(int n, int x, int y, int z) {
        if (this.pages.get(curPage) != null) {
            this.pages.get(curPage).handleTilt(n, x, y, z);
        }
    }
}