org.monome.pages.MIDIKeyboardPage.java Source code

Java tutorial

Introduction

Here is the source code for org.monome.pages.MIDIKeyboardPage.java

Source

/*
 *  MIDIKeyboardPage.java
 * 
 *  Copyright (c) 2008, 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;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.apache.commons.lang.StringEscapeUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * The MIDI Faders page.  Usage information is available at:
 * 
 * http://code.google.com/p/monome-pages/wiki/MIDIKeyboardPage
 *   
 * @author Tom Dinchak, Stephen McLeod
 *
 */

public class MIDIKeyboardPage implements Page, ActionListener {

    /**
     * The MonomeConfiguration that this page belongs to
     */
    MonomeConfiguration monome;

    /**
     * The index of this page (the page number) 
     */
    int index;

    /**
     * The GUI for this page
     */
    JPanel panel;

    /**
     * The selected MIDI channel (8x8 only)
     */
    private int midiChannel = 0;

    /**
     * The octave offset for each row (128 and 256 only) 
     */
    private int[] octave = new int[16];

    private JTextField scaleTF1;
    private JTextField scaleTF2;
    private JTextField scaleTF3;
    private JTextField scaleTF4;
    private JTextField scaleTF5;
    private JTextField scaleTF6;

    private JButton resetScales;

    /**
     * The selected key 
     */
    private int myKey = 0;
    /**
     * The selected scale
     */
    private int myScale = 0;

    /**
     * The semitones between each note in the selected scale 
     */
    private int[][] scales = { { 2, 2, 1, 2, 2, 2, 1 }, //Major scale
            { 2, 1, 2, 2, 1, 2, 2 }, //Natural minor scale         
            { 3, 2, 1, 1, 3, 2, 0 }, //Blues scale
            { 2, 2, 3, 2, 3, 2, 2 }, //Major pentatonic scale
            { 2, 1, 3, 1, 1, 3, 1 }, //Hungarian minor scale
            { 2, 1, 2, 2, 1, 3, 1 }, //Harmonic minor scale      
    };
    private int[][] scalesDefault = { { 2, 2, 1, 2, 2, 2, 1 }, //Major scale
            { 2, 1, 2, 2, 1, 2, 2 }, //Natural minor scale         
            { 3, 2, 1, 1, 3, 2, 0 }, //Blues scale
            { 2, 2, 3, 2, 3, 2, 2 }, //Major pentatonic scale
            { 2, 1, 3, 1, 1, 3, 1 }, //Hungarian minor scale
            { 2, 1, 2, 2, 1, 3, 1 }, //Harmonic minor scale      
    };

    private StringBuffer scaleStr[] = new StringBuffer[6];

    /* ************* alternative scales ***************
        
    E -> E
    G -> G 
    G# -> A
    A -> A#
    B -> B
    C# -> C#
    D -> D 
        
    c#, D, E, G, A, A#, B
        
        
    {1,2,3,2,2,1,2},  // blues 2?
    {1,2,1,3,1,2,2}, // indianish
    {1,3,2,2,2,1,1},  // enigmatic   
    {3,1,1,2,2,1,2} // blues/r&r   
        
    Major scale:    {2,2,1,2,2,2,1}
    Natural minor scale:    {2,1,2,2,1,2,2}
    Harmonic minor scale:    {2,1,2,2,1,3,1}
    Jazz minor scale:    {2,1,2,2,2,2,1}
    Hungarian minor scale:    {2,1,3,1,1,3,1}
    Blues scale:    {3,2,1,1,3,2,0}
    Major pentatonic scale:    {2,2,3,2,3,2,2}   {2,2,3,2,3}
        
    ****************** alternative scales ***************/
    /**
     * Natural:0, Sharp:1, Flat:-1 
     */
    private int accidental = 0;

    /**
     * Number to transpose the keyboard by
     */
    private int transpose = 0;
    private boolean transpose1Hold = false;
    private boolean transpose2Hold = false;

    private int sustain = 0;

    private boolean[][] flashOn = new boolean[16][2];

    /**
     * The starting note for each key (from C-2 to B-2) 
     */
    //private int[] keys = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
    //private int[] keys = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
    private int[] keys = { 60, 62, 64, 65, 67, 69, 71 };
    /**
     * Stores the note on / off state of all MIDI notes
     */
    private int[][] notesOn = new int[16][128];

    private int[][] buttonPress = new int[7][6];

    private Receiver recv;

    private String midiDeviceName;

    private JButton midiOutButton;

    /**
     * The Update Preferences button 
     */
    private JButton updatePrefsButton;

    private Thread thread;
    private boolean blinkNow = true;

    private int pressCount = 0;

    private boolean functionLock = false;

    // tilt stuff 
    private ADCOptions pageADCOptions = new ADCOptions();

    /**
     * The name of the page 
     */
    private String pageName = "MIDI Keyboard";
    private JLabel pageNameLBL;

    /**
     * @param monome The MonomeConfiguration object this page belongs to
     * @param index The index of this page (the page number)
     */
    public MIDIKeyboardPage(MonomeConfiguration monome, int index) {
        this.monome = monome;
        this.index = index;
        this.thread = new Thread(new Flasher());
        this.thread.setDaemon(true);
        this.thread.start();

        if (this.monome.sizeX == 8)
            this.flashOn[4][0] = true;
    }

    private final class Flasher implements Runnable {
        // TODO properly kill this thread when monome configuration is removed
        public void run() {
            try {
                int i = 0;
                while (blinkNow) {
                    if (monome.curPage == index) {
                        //System.out.println(monome.sizeX);
                        for (int x = 0; x < monome.sizeX; x++) {
                            if (flashOn[x][0]) {
                                if (flashOn[x][1]) {
                                    monome.led(x, monome.sizeY - 1, 1, index);
                                    flashOn[x][1] = false;
                                } else {
                                    monome.led(x, monome.sizeY - 1, 0, index);
                                    flashOn[x][1] = true;
                                }
                            }
                        }

                        if (i > 10) {
                            pressCount = 0;
                            i = 0;
                        }

                        if (pressCount == 3 && !functionLock) {
                            pressCount = 0;
                            i = 0;
                            functionLock = true;
                            monome.led(monome.sizeX - 1, monome.sizeY - 1, 1, index);
                        } else if (pressCount == 3 && functionLock) {
                            pressCount = 0;
                            i = 0;
                            functionLock = false;
                            monome.led(monome.sizeX - 1, monome.sizeY - 1, 0, index);
                        }

                        if (pressCount > 0)
                            i++;
                    }

                    Thread.sleep(50);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#handlePress(int, int, int)
     */
    public void handlePress(int x, int y, int value) {
        if (x < 0)
            x = 0;
        if (y < 0)
            y = 0;

        // if this is a 128 or 256 then handle presses differently from a 64 or 40h
        if (this.monome.sizeX > 8) {
            this.handlePress256(x, y, value);
        } else {
            this.handlePress64(x, y, value);
        }
    }

    /**
     * Handles a button press for a 64 / 40h monome
     * 
     * @param x The x value of the button press received
     * @param y The y value of the button press received
     * @param value The type of event (1 = press, 0 = release)
     */
    public void handlePress64(int x, int y, int value) {
        if (value == 1) {
            // select scale or key
            if (y >= 6) {
                if (y == 6 && x < 7 && !this.functionLock) {
                    this.monome.led(this.myKey, 6, 0, this.index);
                    this.myKey = x;
                    this.monome.led(this.myKey, y, 1, this.index);

                } else if (y == 6 && x == 7) {
                    this.monome.led(x, y, 1, this.index);
                    this.sustain = 1;
                    this.doSustain();
                }
                if (y == 7) {
                    if (x == 0) {
                        if (this.transpose > -2)
                            this.transpose--;

                        if (transpose2Hold) {
                            this.transpose = 0;
                            this.stopNotes();
                            transpose2Hold = false;
                            transpose1Hold = false;
                        } else {
                            transpose1Hold = true;
                        }

                        transposeHelper();

                        if (this.transpose < 0) {
                            this.monome.led(x, 7, 1, this.index);
                        }
                    } else if (x == 1) {
                        if (this.transpose < 2)
                            this.transpose++;

                        if (transpose1Hold) {
                            this.transpose = 0;
                            this.stopNotes();
                            transpose2Hold = false;
                            transpose1Hold = false;
                        } else {
                            transpose2Hold = true;
                        }

                        transposeHelper();

                        if (this.transpose > 0) {
                            this.monome.led(x, 7, 1, this.index);
                        }
                    } else if (x == 2 && !this.functionLock) {
                        if (this.accidental == -1) {
                            this.monome.led(2, 7, 0, this.index);
                            this.monome.led(3, 7, 0, this.index);
                            this.accidental = 0;
                        } else {
                            this.monome.led(3, 7, 0, this.index);
                            this.monome.led(x, y, 1, this.index);
                            this.accidental = -1;
                        }
                    } else if (x == 3 && !this.functionLock) {
                        if (this.accidental == 1) {
                            this.monome.led(2, 7, 0, this.index);
                            this.monome.led(3, 7, 0, this.index);
                            this.accidental = 0;
                        } else {
                            this.monome.led(x, y, 1, this.index);
                            this.monome.led(2, 7, 0, this.index);
                            this.accidental = 1;
                        }
                    } else if (x < 7 && !this.functionLock) {
                        int i;

                        if ((this.myScale) % 2 > 0) {
                            i = (this.myScale - 1) / 2 + 4;
                            this.monome.led(i, y, 0, this.index);
                            if (i != x)
                                this.flashOn[i][0] = false;
                        } else {
                            i = this.myScale / 2 + 4;
                            this.monome.led(i, y, 0, this.index);
                            if (i != x)
                                this.flashOn[i][0] = false;
                        }

                        if (this.flashOn[x][0]) {
                            this.flashOn[x][0] = false;
                            this.monome.led(x, y, 1, this.index);
                            this.myScale = ((x - 4) * 2) + 1;
                        } else {
                            this.flashOn[x][0] = true;
                            this.myScale = (x - 4) * 2;
                        }
                    } else if (x == 7) {
                        this.pressCount++;
                    }
                }
                if (!(y == 6 && x == 7) && !(y == 7 && x < 2) && !this.functionLock) {

                    this.stopNotes();
                    retriggerNotes();
                }

                // select the midi channel
            } else {
                if (x == 7 && y < 4 && !this.functionLock) {
                    this.midiChannel = y;
                    for (int i = 0; i < 4; i++) {
                        if (this.midiChannel == i) {
                            this.monome.led(x, i, 1, this.index);
                        } else {
                            this.monome.led(x, i, 0, this.index);
                        }
                    }
                    if (!(y == 6 && x == 7) && !(y == 7 && x < 2) && !this.functionLock)
                        this.stopNotes();
                } else if (x == 7 && y == 4) {
                    //set the offset for the adc sends
                    if (this.pageADCOptions.getAdcTranspose() == 1) {
                        this.monome.led(7, 4, 0, this.index);
                        this.monome.led(7, 5, 0, this.index);
                        this.pageADCOptions.setAdcTranspose(0);
                    } else if (this.pageADCOptions.getAdcTranspose() == 2) {
                        this.monome.led(7, 4, 1, this.index);
                        this.monome.led(7, 5, 0, this.index);
                        this.pageADCOptions.setAdcTranspose(1);
                    } else {
                        this.monome.led(x, y, 1, this.index);
                        this.pageADCOptions.setAdcTranspose(1);
                    }
                } else if (x == 7 && y == 5) {
                    this.monome.led(7, 4, 1, this.index);
                    this.monome.led(7, 5, 1, this.index);
                    this.pageADCOptions.setAdcTranspose(2);
                }
            }
        } else {
            if (y == 6 && x == 7) {
                //turn off sustain
                this.monome.led(x, y, 0, this.index);
                this.sustain = 0;
                this.doSustain();
            } else if (y == 7 && x == 0) {
                transpose1Hold = false;
            } else if (y == 7 && x == 1) {
                transpose2Hold = false;
            }
        }

        // for presses and releases in the keyboard area, send the note on message on press
        // and send the note off on message release
        if (y < 6 && x < 7) {
            int velocity = value * 127;
            int channel = this.midiChannel;
            int note_num = this.getNoteNumber(x) + ((y - 3) * 12);

            if (note_num > 127)
                note_num = 127;
            if (note_num < 0)
                note_num = 0;

            this.playNote(note_num, velocity, channel);
            this.notesOn[channel][note_num] = value;
            this.buttonPress[x][y] = value;
            this.monome.led(x, y, value, this.index);
        }
    }

    //allows for playing notes by pressing different key/accidental/scale buttons
    private void retriggerNotes() {
        for (int i = 0; i < 128; i++) {
            this.notesOn[this.midiChannel][i] = 0;
        }
        for (int x = 0; x < 7; x++) {
            for (int y = 0; y < 6; y++) {
                if (this.buttonPress[x][y] == 1) {
                    int note_num = this.getNoteNumber(x) + ((y - 3) * 12);
                    if (note_num > 127)
                        note_num = 127;
                    if (note_num < 0)
                        note_num = 0;

                    this.notesOn[this.midiChannel][note_num] = 1;
                    this.playNote(note_num, 127, this.midiChannel);
                }
            }
        }
    }

    private void transposeHelper() {
        if (transpose == -1) {
            flashOn[0][0] = true;
        } else if (transpose == 1) {
            flashOn[1][0] = true;
        } else {
            flashOn[1][0] = false;
            flashOn[0][0] = false;
        }
        if (this.transpose == 0) {
            this.monome.led(0, 7, 0, this.index);
            this.monome.led(1, 7, 0, this.index);
        }
    }

    /**
     * Handles a button press for a 128 / 256 monome
     * 
     * @param x The x value of the button press received
     * @param y The y value of the button press received
     * @param value The type of event (1 = press, 0 = release)
     */
    public void handlePress256(int x, int y, int value) {

        if (value == 1) {
            // bottom row - set the key or scale
            if (y == (this.monome.sizeY - 1)) {
                if (x < 7 && !this.functionLock) {
                    this.monome.led(this.myKey, y, 0, this.index);
                    this.myKey = x;
                    this.monome.led(this.myKey, y, 1, this.index);
                } else if (x == 7 && !this.functionLock) {
                    if (this.accidental == -1) {
                        this.monome.led(8, y, 0, this.index);
                        this.monome.led(7, y, 0, this.index);
                        this.accidental = 0;
                    } else {
                        this.monome.led(8, y, 0, this.index);
                        this.monome.led(x, y, 1, this.index);
                        this.accidental = -1;
                    }
                } else if (x == 8 && !this.functionLock) {
                    if (this.accidental == 1) {
                        this.monome.led(8, y, 0, this.index);
                        this.monome.led(7, y, 0, this.index);
                        this.accidental = 0;
                    } else {
                        this.monome.led(x, y, 1, this.index);
                        this.monome.led(7, y, 0, this.index);
                        this.accidental = 1;
                    }
                } else if (x < 15 && !this.functionLock) {
                    this.monome.led(this.myScale + 9, y, 0, this.index);
                    this.myScale = (x - 9);
                    this.monome.led(this.myScale + 9, y, 1, this.index);
                }

                if (x == 15) {
                    this.pressCount++;
                }

                this.stopNotes();

                // set the octave offset
            } else {

                // minus 2 octaves
                if (x == 14 && !this.functionLock) {
                    if (this.octave[y] == -1) {
                        return;
                    } else {
                        this.octave[y] -= 1;
                    }
                    if (this.octave[y] == 0) {
                        this.monome.led(14, y, 0, this.index);
                        this.monome.led(15, y, 0, this.index);
                    }
                    if (this.octave[y] == -1) {
                        this.monome.led(14, y, 1, this.index);
                        this.monome.led(15, y, 0, this.index);
                    }
                    return;
                }

                // plus 2 octaves
                if (x == 15 && !this.functionLock) {
                    if (this.octave[y] == 1) {
                        return;
                    } else {
                        this.octave[y] += 1;
                    }
                    if (this.octave[y] == 0) {
                        this.monome.led(14, y, 0, this.index);
                        this.monome.led(15, y, 0, this.index);
                    }
                    if (this.octave[y] == 1) {
                        this.monome.led(14, y, 0, this.index);
                        this.monome.led(15, y, 1, this.index);
                    }
                    return;
                }
                this.stopNotes();
            }
        }

        // play the note
        if (y != (this.monome.sizeY - 1) && x < 14) {
            int velocity = value * 127;
            int channel = (int) Math.floor(y / 3);
            int note_num = this.getNoteNumber(x) + (this.octave[y] * 24);
            this.playNote(note_num, velocity, channel);

            if (note_num > 127)
                note_num = 127;
            if (note_num < 0)
                note_num = 0;

            if (x < 14) {
                this.monome.led(x, y, value, this.index);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#handleTick()
     */
    public void handleTick() {
        return;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#handleReset()
     */
    public void handleReset() {
        this.redrawMonome();
    }

    private void stopNotes() {
        ShortMessage note_out = new ShortMessage();
        for (int chan = 0; chan < 16; chan++) {
            for (int i = 0; i < 128; i++) {
                if (this.notesOn[chan][i] == 1) {
                    try {
                        note_out.setMessage(ShortMessage.NOTE_OFF, chan, i, 0);
                        recv.send(note_out, -1);
                    } catch (InvalidMidiDataException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * Plays a MIDI note.  0 velocity will send a note off, and > 0 velocity will send a note on.
     * 
     * @param note_num
     * @param velocity
     * @param channel
     */
    public void playNote(int note_num, int velocity, int channel) {
        ShortMessage note_out = new ShortMessage();
        if (this.recv == null) {
            return;
        }
        try {
            if (velocity == 0) {
                note_out.setMessage(ShortMessage.NOTE_OFF, channel, note_num, velocity);
            } else {
                note_out.setMessage(ShortMessage.NOTE_ON, channel, note_num, velocity);
            }
            recv.send(note_out, -1);

        } catch (InvalidMidiDataException e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends CC64 (sustain).  0 velocity will send a note off, and > 0 velocity will send a note on.
     *
     */
    public void doSustain() {
        ShortMessage sustain_out = new ShortMessage();
        if (this.recv == null) {
            return;
        }
        try {
            if (this.sustain == 1) {
                sustain_out.setMessage(ShortMessage.CONTROL_CHANGE, this.midiChannel, 64, 127);
            } else {
                sustain_out.setMessage(ShortMessage.CONTROL_CHANGE, this.midiChannel, 64, 0);
            }
            recv.send(sustain_out, -1);

        } catch (InvalidMidiDataException e) {
            e.printStackTrace();
        }
    }

    /**
     * Convert a button press to a MIDI note number.
     * 
     * @param y The y value of the button pressed
     * @return The MIDI note number
     */
    public int getNoteNumber(int y) {
        int offset = 0;
        int note;

        int multiplier = this.transpose * 12;

        if (y >= 7) {
            y -= 7;
            offset = 12;
        }

        note = this.keys[this.myKey];

        for (int i = 0; i < y; i++) {
            note += this.scales[this.myScale][i];
        }

        note += offset;
        note += (multiplier + this.accidental);

        return note;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#redrawMonome()
     */
    public void redrawMonome() {
        // for 128 / 256 monomes
        if (this.monome.sizeX > 8) {
            this.redrawMonome256();
            // for 64 / 40h monomes
        } else {
            this.redrawMonome64();
        }
    }

    /**
     * Redraw this page on a 64 or 40h monome.
     */
    public void redrawMonome64() {
        // everything off except the midi channel selection, the key and the scale
        for (int x = 0; x < this.monome.sizeX; x++) {
            for (int y = 0; y < this.monome.sizeY; y++) {
                if (x == 7 && y < 4) {
                    if (this.midiChannel == y) {
                        this.monome.led(x, y, 1, this.index);
                    } else {
                        this.monome.led(x, y, 0, this.index);
                    }
                } else if (x == 7 && y == 4) {
                    if (this.pageADCOptions.getAdcTranspose() == 1) {
                        this.monome.led(x, y, 1, this.index);
                    } else {
                        this.monome.led(x, y, 0, this.index);
                    }

                } else if (x == 7 && y == 5) {
                    if (this.pageADCOptions.getAdcTranspose() == 2) {
                        this.monome.led(7, 4, 1, this.index);
                        this.monome.led(7, 5, 1, this.index);
                    } else {
                        this.monome.led(x, y, 0, this.index);
                    }
                } else if (y >= 6) {
                    if (y == 6 && this.myKey < 8 && this.myKey == x) {
                        this.monome.led(x, y, 1, this.index);
                    } else if (y == 6 && x == 7) {
                        if (this.sustain == 1)
                            this.monome.led(x, y, 1, this.index);
                    } else if (y == 7 && x == 0) {
                        if (this.transpose < 0)
                            this.monome.led(x, y, 1, this.index);
                        else
                            this.monome.led(x, y, 0, this.index);
                    } else if (y == 7 && x == 1) {
                        if (this.transpose > 0)
                            this.monome.led(x, y, 1, this.index);
                        else
                            this.monome.led(x, y, 0, this.index);
                    } else if (y == 7 && x == 2) {
                        if (this.accidental == -1)
                            this.monome.led(x, y, 1, this.index);
                        else
                            this.monome.led(x, y, 0, this.index);
                    } else if (y == 7 && x == 3) {
                        if (this.accidental == 1)
                            this.monome.led(x, y, 1, this.index);
                        else
                            this.monome.led(x, y, 0, this.index);
                    } else if (y == 7 && x < 7) {
                        int i;
                        if ((this.myScale) % 2 > 0) {
                            i = (this.myScale - 1) / 2 + 4;
                            if (i != x)
                                this.monome.led(x, y, 0, this.index);
                            else
                                this.monome.led(x, y, 1, this.index);
                        } else {
                            i = this.myScale / 2 + 4;
                            if (i != x)
                                this.monome.led(x, y, 0, this.index);
                            else
                                this.monome.led(x, y, 1, this.index);
                        }
                    } else if (y == 7 && x == 7) {
                        if (this.functionLock)
                            this.monome.led(x, y, 1, this.index);
                        else
                            this.monome.led(x, y, 0, this.index);
                    } else {
                        this.monome.led(x, y, 0, this.index);
                    }
                } else {
                    this.monome.led(x, y, 0, this.index);
                }
            }
        }
    }

    /**
     * Redraws this page for a 128 or 256 monome.
     */
    public void redrawMonome256() {
        // everything off except the key/scale selection and the octave offsets
        for (int x = 0; x < this.monome.sizeX; x++) {
            for (int y = 0; y < this.monome.sizeY; y++) {
                if (y == (this.monome.sizeY - 1)) {
                    if (x == this.myKey) {
                        this.monome.led(x, y, 1, this.index);
                    } else if (x == 7 && this.accidental == -1) {
                        this.monome.led(x, y, 1, this.index);
                    } else if (x == 8 && this.accidental == 1) {
                        this.monome.led(x, y, 1, this.index);
                    } else if (x >= 9 && (x - 9) == this.myScale) {
                        this.monome.led(x, y, 1, this.index);
                    } else if (x == 15 && this.functionLock) {
                        this.monome.led(x, y, 1, this.index);
                    } else {
                        this.monome.led(x, y, 0, this.index);
                    }
                } else if (y != (this.monome.sizeY - 1) && x == 14) {
                    if (this.octave[y] == -1) {
                        this.monome.led(x, y, 1, this.index);
                    }
                    if (this.octave[y] == 0) {
                        this.monome.led(x, y, 0, this.index);
                    }
                } else if (y != (this.monome.sizeY - 1) && x == 15) {
                    if (this.octave[y] == 1) {
                        this.monome.led(x, y, 1, this.index);
                    }
                    if (this.octave[y] == 0) {
                        this.monome.led(x, y, 0, this.index);
                    }
                } else {
                    this.monome.led(x, y, 0, this.index);
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#getName()
     */

    public String getName() {
        return this.pageName;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#setName()
     */
    public void setName(String name) {
        this.pageName = name;
        this.pageNameLBL.setText("Page " + (this.index + 1) + ": " + pageName);
        this.monome.setJMenuBar(this.monome.createMenuBar());
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#getPanel()
     */
    public JPanel getPanel() {
        if (this.panel != null) {
            return this.panel;
        }

        JPanel panel = new JPanel();
        panel.setLayout(null);
        panel.setPreferredSize(new java.awt.Dimension(220, 240));

        pageNameLBL = new JLabel("Page " + (this.index + 1) + ": " + pageName);
        panel.add(pageNameLBL);
        pageNameLBL.setBounds(0, 0, 245, 14);

        JLabel midiout = new JLabel("<html>MIDI Out: " + this.midiDeviceName + "</html>");
        panel.add(midiout);
        midiout.setBounds(20, 25, 200, 35);

        JLabel scalelbl = new JLabel("Scales:");
        panel.add(scalelbl);
        scalelbl.setBounds(20, 75, 100, 14);

        resetScales = new JButton("Reset Scales");
        resetScales.addActionListener(this);
        panel.add(resetScales);
        resetScales.setBounds(80, 75, 120, 20);

        scaleTF1 = new JTextField();
        scaleTF2 = new JTextField();
        scaleTF3 = new JTextField();
        scaleTF4 = new JTextField();
        scaleTF5 = new JTextField();
        scaleTF6 = new JTextField();

        panel.add(scaleTF1);
        scaleTF1.setBounds(20, 100, 80, 20);
        panel.add(scaleTF2);
        scaleTF2.setBounds(120, 100, 80, 20);
        panel.add(scaleTF3);
        scaleTF3.setBounds(20, 125, 80, 20);
        panel.add(scaleTF4);
        scaleTF4.setBounds(120, 125, 80, 20);
        panel.add(scaleTF5);
        scaleTF5.setBounds(20, 150, 80, 20);
        panel.add(scaleTF6);
        scaleTF6.setBounds(120, 150, 80, 20);

        //populate the text fields with the selected scales
        getScales();

        midiOutButton = new JButton("Set MIDI Output");
        midiOutButton.addActionListener(this);
        panel.add(midiOutButton);
        midiOutButton.setBounds(20, 175, 180, 20);

        updatePrefsButton = new JButton("Update Preferences");
        updatePrefsButton.addActionListener(this);
        panel.add(this.updatePrefsButton);
        updatePrefsButton.setBounds(20, 200, 180, 20);

        this.panel = panel;
        return panel;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#send(javax.sound.midi.MidiMessage, long)
     */
    public void send(MidiMessage message, long timeStamp) {
        return;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#toXml()
     */
    public String toXml() {
        String xml = "";
        xml += "      <name>MIDI Keyboard</name>\n";
        xml += "      <selectedmidioutport>" + StringEscapeUtils.escapeXml(this.midiDeviceName)
                + "</selectedmidioutport>\n";
        xml += "      <pageName>" + this.pageName + "</pageName>\n";
        xml += "      <scaleStr1>" + this.scaleStr[0].toString() + "</scaleStr1>\n";
        xml += "      <scaleStr2>" + this.scaleStr[1].toString() + "</scaleStr2>\n";
        xml += "      <scaleStr3>" + this.scaleStr[2].toString() + "</scaleStr3>\n";
        xml += "      <scaleStr4>" + this.scaleStr[3].toString() + "</scaleStr4>\n";
        xml += "      <scaleStr5>" + this.scaleStr[4].toString() + "</scaleStr5>\n";
        xml += "      <scaleStr6>" + this.scaleStr[5].toString() + "</scaleStr6>\n";

        xml += "      <ccoffset>" + this.pageADCOptions.getCcOffset() + "</ccoffset>\n";
        xml += "      <sendADC>" + this.pageADCOptions.isSendADC() + "</sendADC>\n";
        xml += "      <midiChannelADC>" + this.pageADCOptions.getMidiChannel() + "</midiChannelADC>\n";
        xml += "      <adcTranspose>" + this.pageADCOptions.getAdcTranspose() + "</adcTranspose>\n";
        xml += "      <recv>" + this.pageADCOptions.getRecv() + "</recv>\n";

        return xml;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.getActionCommand());
        if (e.getActionCommand().equals("Set MIDI Output")) {
            String[] midiOutOptions = this.monome.getMidiOutOptions();
            String deviceName = (String) JOptionPane.showInputDialog(this.monome, "Choose a MIDI Output to use",
                    "Set MIDI Output", JOptionPane.PLAIN_MESSAGE, null, midiOutOptions, "");

            if (deviceName == null) {
                return;
            }

            this.addMidiOutDevice(deviceName);

        }
        if (e.getActionCommand().equals("Update Preferences")) {
            String s[] = new String[6];
            s[0] = this.scaleTF1.getText();
            s[1] = this.scaleTF2.getText();
            s[2] = this.scaleTF3.getText();
            s[3] = this.scaleTF4.getText();
            s[4] = this.scaleTF5.getText();
            s[5] = this.scaleTF6.getText();
            setScales(s);
        }

        if (e.getActionCommand().equals("Reset Scales")) {
            this.scales = this.scalesDefault;
            this.getScales();
        }
    }

    /**
     * gets the available scales and displays them on the gui 
     */
    public void getScales() {
        for (int i = 0; i < 6; i++) {
            this.scaleStr[i] = new StringBuffer();
            for (int j = 0; j < 6; j++) {
                this.scaleStr[i].append(Integer.toString(scales[i][j]) + ",");
            }
            this.scaleStr[i].append(Integer.toString(scales[i][6]));
        }
        this.scaleTF1.setText(scaleStr[0].toString());
        this.scaleTF2.setText(scaleStr[1].toString());
        this.scaleTF3.setText(scaleStr[2].toString());
        this.scaleTF4.setText(scaleStr[3].toString());
        this.scaleTF5.setText(scaleStr[4].toString());
        this.scaleTF6.setText(scaleStr[5].toString());
    }

    /**
     * sets the scales to those entered in the gui
     */
    public void setScales(String s[]) {
        scaleStr[0].replace(0, 20, s[0]);
        scaleStr[1].replace(0, 20, s[1]);
        scaleStr[2].replace(0, 20, s[2]);
        scaleStr[3].replace(0, 20, s[3]);
        scaleStr[4].replace(0, 20, s[4]);
        scaleStr[5].replace(0, 20, s[5]);

        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 7; j++) {
                try {
                    if (this.scaleStr[i].length() < 13)
                        throw new NumberFormatException();
                    scales[i][j] = Integer.parseInt(this.scaleStr[i].substring(j * 2, j * 2 + 1));
                    if (j < 5 && this.scaleStr[i].charAt(j * 2 + 1) != ',')
                        throw new NumberFormatException();

                } catch (java.lang.NumberFormatException nfe) {
                    JOptionPane.showMessageDialog(this.monome, "Scale input must be formatted \"#,#,#,#,#,#,#\".",
                            "Input Error!", 1);
                    return;
                }
            }
        }
    }

    /* (non-Javadoc)
      * @see org.monome.pages.Page#addMidiOutDevice(java.lang.String)
     */
    public void addMidiOutDevice(String deviceName) {
        this.recv = this.monome.getMidiReceiver(deviceName);
        this.midiDeviceName = deviceName;
        this.midiOutButton.removeActionListener(this);
        this.panel.removeAll();
        this.panel = null;
        this.monome.redrawPanel();
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#getCacheDisabled()
     */
    public boolean getCacheDisabled() {
        return false;
    }

    /* (non-Javadoc)
     * @see org.monome.pages.Page#destroyPage()
     */
    public void destroyPage() {
        this.blinkNow = false;
        return;
    }

    public void clearPanel() {
        this.panel = null;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public void handleADC(int adcNum, float value) {
        if (this.pageADCOptions.isSendADC() && this.monome.adcObj.isEnabled()) {
            int midi = this.pageADCOptions.getMidiChannel();
            if (midi == -1) {
                this.monome.adcObj.sendCC(this.recv, this.midiChannel, this.pageADCOptions.getCcADC(), monome,
                        adcNum, value);
            } else {
                this.monome.adcObj.sendCC(this.recv, midi, this.pageADCOptions.getCcADC(), monome, adcNum, value);
            }
        }
    }

    public void handleADC(float x, float y) {
        if (this.pageADCOptions.isSendADC() && this.monome.adcObj.isEnabled()) {
            int midi = this.pageADCOptions.getMidiChannel();
            if (midi == -1) {
                this.monome.adcObj.sendCC(this.recv, this.midiChannel, this.pageADCOptions.getCcADC(), monome, x,
                        y);
            } else {
                this.monome.adcObj.sendCC(this.recv, midi, this.pageADCOptions.getCcADC(), monome, x, y);
            }
        }
    }

    public boolean isTiltPage() {
        return true;
    }

    public ADCOptions getAdcOptions() {
        return this.pageADCOptions;
    }

    public void setAdcOptions(ADCOptions options) {
        this.pageADCOptions = options;
    }

    public void configure(Element pageElement) {
        NodeList nl = pageElement.getElementsByTagName("pageName");
        Element el = (Element) nl.item(0);
        if (el != null) {
            nl = el.getChildNodes();
            String name = ((Node) nl.item(0)).getNodeValue();
            this.setName(name);
        }

        String s[] = new String[6];
        for (int i = 1; i < 7; i++) {
            nl = pageElement.getElementsByTagName("scaleStr" + i);
            el = (Element) nl.item(0);
            if (el != null) {
                nl = el.getChildNodes();
                s[i - 1] = ((Node) nl.item(0)).getNodeValue();
            }
        }
        if (el != null) {
            this.setScales(s);
            this.getScales();
        }

        nl = pageElement.getElementsByTagName("ccoffset");
        el = (Element) nl.item(0);
        if (el != null) {
            nl = el.getChildNodes();
            String ccOffset = ((Node) nl.item(0)).getNodeValue();
            this.pageADCOptions.setCcOffset(Integer.parseInt(ccOffset));
        }

        nl = pageElement.getElementsByTagName("sendADC");
        el = (Element) nl.item(0);
        if (el != null) {
            nl = el.getChildNodes();
            String sendADC = ((Node) nl.item(0)).getNodeValue();
            this.pageADCOptions.setSendADC(Boolean.parseBoolean(sendADC));
        }

        nl = pageElement.getElementsByTagName("adcTranspose");
        el = (Element) nl.item(0);
        if (el != null) {
            nl = el.getChildNodes();
            String adcTranspose = ((Node) nl.item(0)).getNodeValue();
            this.pageADCOptions.setAdcTranspose(Integer.parseInt(adcTranspose));
        }

        nl = pageElement.getElementsByTagName("midiChannelADC");
        el = (Element) nl.item(0);
        if (el != null) {
            nl = el.getChildNodes();
            String midiChannelADC = ((Node) nl.item(0)).getNodeValue();
            this.pageADCOptions.setMidiChannel(Integer.parseInt(midiChannelADC));
        }

        nl = pageElement.getElementsByTagName("recv");
        el = (Element) nl.item(0);
        if (el != null) {
            nl = el.getChildNodes();
            String recv = ((Node) nl.item(0)).getNodeValue();
            this.pageADCOptions.setRecv(recv);
        }
    }
}