amahlaka_MidiKatapultUpdate/Controls.pde
import java.awt.Robot;
import java.awt.event.InputEvent.*;
import java.awt.AWTException;

import java.awt.event.KeyEvent;
import java.util.Date;
class Control {
  int x;
  int y;
  int xsize;
  int ysize;
  int type;
  int state;
  int number;
  int value;
  int velocity;
  int softwarevalue;
  boolean persistence;
  boolean sn;
  int timeout;
  int idlecolor;
  int activecolor;
  int controlID;
  int page;
  int channel = customChannel;
  int takeover;
  int updatedelay = 50;
  boolean TLOCK = false;
  boolean TAKEOVER = false;
  long threadsSpawned = 0;
  long threadsFinished = 0;
  Control owner;
  boolean isChained = false;
  int numberOfChains = 0;
  boolean chainSendFlags[] = new boolean[MAXCHAINS];
  Control chains[] = new Control[MAXCHAINS];
  int maxvalue = 127;
  void up(){ledOn(x, y, activecolor);}
  void down(){}
  void on(){display(x, y, activecolor); }
  void off(){display(x, y, idlecolor);}
  void send(){}
  void setValue(int ivalue){}
  void takeoverSetValue(int ivalue){}
  void setMaxValue(int imaxvalue){}
  void nakedSetValue(int ivalue) {}
  void update(){}
  void cancelSchedule(){}
  
  
  void chainTo(Control control, boolean send) {
    chains[numberOfChains] = control;
    //consoledebug("Chaining "+this+" to "+control);
    chainSendFlags[numberOfChains] = send;
    numberOfChains++;
    isChained = true;
  }
  
  void propagateToChainedControls() {
    if(isChained) {
      for (int i = 0; i < numberOfChains; i++) {
        //println("Chained update to c on "+chains[i].page+". now on "+selectedPage);
        chains[i].setValue(value);
        if (chainSendFlags[i]) chains[i].send();
        if (chains[i].page == selectedPage) chains[i].update();
      }
    }
  }
}

class GridSegment extends Control {
  
  void on() {
    println(this+"x="+this.x+" y="+this.y+" on()");

    
    ledOn(x, y, activecolor);
    
  }
  
  void off() {
    println(this+"x="+this.x+" y="+this.y+" off()");

    ledOff(x, y);
  }
}

class PageButton extends GridSegment {
  int number;
  
  PageButton(int inumber) {
    page = selectedPage;
    //println(this+" on page "+page);
    number = inumber;
    idlecolor = PAGEBUTTONIDLECOLOR;
    activecolor = PAGEBUTTONACTIVECOLOR;
    grid[number] = this;
    ledOn((number % 8), (int)((float)number / 8), idlecolor);
  }
  
  void down() {
    ledOn((number % 8), (int)((float)number / 8), activecolor);
    loadLayout(number+1);
  }
  
  void update() {
    x = 8;
    y = 7;
    off();
  }
}

class LED extends GridSegment {
  LED(int ix, int iy) {
    controlID = (int)random(1000);
    page = selectedPage;
    println(this+" on page "+page);
    x = ix;
    y = iy;
    value = 0;
    state = OFF;
    timeout = 0;
    grid[y*8+x] = this;
    idlecolor = INDICATOROFFCOLOR;
    activecolor = INDICATORONCOLOR;
    ledOn(x, y, idlecolor);
  }
  
  void send() {
    controlOut(x, y, state);
  }
  
  void down() {
    
  }
  
  void up() {

  }
  
  void update() {
    if (value == 0) {
      off();
    }
    if (value > 0) {
      on();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (ivalue > 0) { value = ivalue; state = ON; } else { value = OFF; state = OFF; }
    propagateToChainedControls();
  }
}

class Button extends GridSegment {
  Button(int ix, int iy, int itype) {
    controlID = (int)random(1000);
    page = selectedPage;
    persistence = false;
    sn = true;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    type = itype;
    value = 0;
    velocity = -1;
    state = OFF;
    timeout = 0;
    if (x < 8) {
      grid[y*8+x] = this;
    }
    if (x == 8 && y < 7) {
      //debug("Mapping side button:"+this);
      grid[65+y] = this;
    }
    if (type == HOLD) {
      idlecolor = BUTTONIDLECOLOR;
      activecolor = HOLDONCOLOR;
    }
    if (type == TOGGLE) {
      idlecolor = TOGGLEOFFCOLOR;
      activecolor = TOGGLEONCOLOR;
    }
    ledOn(x, y, idlecolor);
  }
  
  void setVelocity(int vel) {
    velocity = vel;
  }
  
  void send() {
    debug("Velocity is "+velocity);
    if (channel != 0) outputChannel = channel-1;
    if (velocity == -1) controlOut(x, y, state);
    if (velocity != -1 && state == ON) controlOut(x, y, velocity);
    if (velocity != -1 && state == OFF) controlOut(x, y, state); 
  }
  
  void down() {
    sn = false;
    if (type == HOLD) {
      state = ON;
      setValue(ON);
      send();
      if (!persistence) on();
    }
    if (type == TOGGLE) {
      if (state == OFF) {
        state = ON;
        send();
        if (!persistence) on();
        setValue(ON);
      } else if (state == ON) {
        state = OFF;
        send();
        if (!persistence) off();
        setValue(OFF);
      }
    }
  }
  
  void up() {
    sn = false;
    if (type == HOLD) {
      //state = OFF;
      setValue(OFF);
      send();
      if (!persistence) off();
    }
  }
  
  void update() {
    debug("sn "+sn); //<>//
    if (value == 0) {
      off();
    }
    if (value == 127 || softwarevalue == 127) { //<>//
      on();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    softwarevalue = ivalue;
    if (ivalue > 0) { value = 127; state = ON; } else { value = 0; state = OFF; }
    propagateToChainedControls();
    sn = true;
  }
}

class Note extends GridSegment {
  int note;
  int vel;
  boolean sendoff = false;
  boolean toggle = false;
  
  Note(int ix, int iy, int octave, String note, int vel) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    value = 0;
    state = OFF;
    timeout = 0;
    idlecolor = NOTEIDLECOLOR;
    activecolor = NOTEACTIVECOLOR;
    this.note = addOctave(parseNote(note), octave);
    this.vel = vel;
    
    if (x < 8) {
      grid[y*8+x] = this;
    }
    if (x == 8 && y < 7) {
      //debug("Mapping side button:"+this);
      grid[65+y] = this;
    }
    
    ledOn(x, y, idlecolor);

}
  
  Note(int ix, int iy, int midi, int vel) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    value = 0;
    state = OFF;
    timeout = 0;
    idlecolor = NOTEIDLECOLOR;
    activecolor = NOTEACTIVECOLOR;
    this.note = midi;
    this.vel = vel;
    
    if (x < 8) {
      grid[y*8+x] = this;
    }
    if (x == 8 && y < 7) {
      //debug("Mapping side button:"+this);
      grid[65+y] = this;
    }
    
    ledOn(x, y, idlecolor);
  }
  
  int parseNote(String note) {
    note = note.toLowerCase();
    if (note.equals("c")) return 0;
    if (note.equals("c#")) return 1;
    if (note.equals("d")) return 2;
    if (note.equals("d#")) return 3;
    if (note.equals("e")) return 4;
    if (note.equals("f")) return 5;
    if (note.equals("f#")) return 6;
    if (note.equals("g")) return 7;
    if (note.equals("g#")) return 8;
    if (note.equals("a")) return 9;
    if (note.equals("a#")) return 10;
    if (note.equals("b")) return 11;
    return 0;
  }
  
  int addOctave(int note, int octave) {
    octave = octave+1;
    return note+(octave*12);
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    if (sendoff) {
      noteOut(note, 0);
      sendoff = false;
    } else {
      //debug("sendOn");
      noteOut(note, vel);
    }
  }
  
  void sendOff() {
    if (channel != 0) outputChannel = channel-1;
    //debug("sendOff");
    noteOut(note, 0);
  }
  
  void down() {
    if (!toggle) {
      send();
      on();
    } else {
      if (state == OFF) {
        send();
        on();
        state = ON;
      } else if (state == ON) {
        sendOff();
        off();
        state = OFF;
      }
    }
  }
  
  void up() {
    if (!toggle) {
      sendOff();
      off();
    }
  }
  
  void update() {
    if (value == 0) {
      off();
    }
    if (value == 127) {
      on();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    value = ivalue;
    if (ivalue == 0) { 
      sendoff = true;    
    }
    propagateToChainedControls();
  }
}

class CC extends GridSegment {
  int note;
  int vel;
  boolean toggle;
  boolean sendoff = false;
  
  CC(int ix, int iy, int cc, int vel) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    value = 0;
    state = OFF;
    timeout = 0;
    idlecolor = CCIDLECOLOR;
    activecolor = CCACTIVECOLOR;
    this.note = cc;
    this.vel = vel;
    toggle = false;
    
    if (x < 8) {
      grid[y*8+x] = this;
    }
    if (x == 8 && y < 7) {
      //debug("Mapping side button:"+this);
      grid[65+y] = this;
    }
    
    ledOn(x, y, idlecolor);
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    if (sendoff) {
      sendOff();
      sendoff = false;
    } else {
      //debug("sendOn");
      controlOut(note, vel);
    }
  }
  
  void sendOff() {
    //debug("sendOff");
    //controlOut(note, 0);
  }
  
  void down() {
      send();
      on();
  }
  
  void up() {
      sendOff();
      off();
  }
  
  void update() {
    if (value == 0) {
      off();
    }
    if (value == 127) {
      on();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    value = ivalue;
    if (ivalue == 0) { 
      sendoff = true;    
    }
    propagateToChainedControls();
  }
}

class PC extends GridSegment {
  int note;
  int vel;
  boolean sendoff = false;
  
  PC(int ix, int iy, int program) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    value = 0;
    state = OFF;
    timeout = 0;
    idlecolor = PCIDLECOLOR;
    activecolor = PCACTIVECOLOR;
    this.note = program;
    
    if (x < 8) {
      grid[y*8+x] = this;
    }
    if (x == 8 && y < 7) {
      debug("Mapping side button:"+this);
      grid[65+y] = this;
    }
    
    ledOn(x, y, idlecolor);
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    if (sendoff) {
      sendOff();
      sendoff = false;
    } else {
      //debug("sendOn");
      programOut(note);
    }
  }
  
  void sendOff() {
    //debug("sendOff");
    //controlOut(note, 0);
  }
  
  void down() {
      send();
      on();
  }
  
  void up() {
      sendOff();
      off();

  }
  
  void update() {
    if (value == 0) { //<>//
      off();
    }
    if (value == 127) {
      on();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    value = ivalue;
    if (ivalue == 0) { 
      sendoff = true;    
    }
    propagateToChainedControls();
  }
}

class Kbd extends GridSegment {
  int note;
  int vel;
  boolean sendoff = false;
  Robot robot;
  int keys[];

  Kbd(int ix, int iy, String keystring) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    value = 0;
    state = OFF;
    timeout = 0;
    idlecolor = KBDIDLECOLOR;
    activecolor = KBDACTIVECOLOR;
    try {
      robot = new Robot();
    } catch (AWTException e) {
      debug("Exception while creating robot "+e);
    }
    String keysS[] = split(keystring, "+");
    keys = new int[keysS.length];
    for (int i = 0; i < keys.length; i++) {
      keys[i] = parseKey(keysS[i]);
    }
    
    if (x < 8) {
      grid[y*8+x] = this;
    }
    if (x == 8 && y < 7) {
      //debug("Mapping side button:"+this);
      grid[65+y] = this;
    }
    
    ledOn(x, y, idlecolor); //<>//
  }
  
  int parseKey(String keystr) {
    if (keystr.equals("shift")) return KeyEvent.VK_SHIFT;
    if (keystr.equals("control")) return KeyEvent.VK_CONTROL;
    if (keystr.equals("alt")) return KeyEvent.VK_ALT;
    if (keystr.equals("altgr")) return KeyEvent.VK_ALT_GRAPH;
    if (keystr.equals("command")) return KeyEvent.VK_META;
    if (keystr.equals("1")) return KeyEvent.VK_1;
    if (keystr.equals("2")) return KeyEvent.VK_2;
    if (keystr.equals("3")) return KeyEvent.VK_3;
    if (keystr.equals("4")) return KeyEvent.VK_4;
    if (keystr.equals("5")) return KeyEvent.VK_5;
    if (keystr.equals("6")) return KeyEvent.VK_6;
    if (keystr.equals("7")) return KeyEvent.VK_7;
    if (keystr.equals("8")) return KeyEvent.VK_8;
    if (keystr.equals("9")) return KeyEvent.VK_9;
    if (keystr.equals("0")) return KeyEvent.VK_0;
    if (keystr.equals("a")) return KeyEvent.VK_A;
    if (keystr.equals("b")) return KeyEvent.VK_B;
    if (keystr.equals("c")) return KeyEvent.VK_C;
    if (keystr.equals("d")) return KeyEvent.VK_D;
    if (keystr.equals("e")) return KeyEvent.VK_E;
    if (keystr.equals("f")) return KeyEvent.VK_F;
    if (keystr.equals("g")) return KeyEvent.VK_G;
    if (keystr.equals("h")) return KeyEvent.VK_H;
    if (keystr.equals("i")) return KeyEvent.VK_I;
    if (keystr.equals("j")) return KeyEvent.VK_J;
    if (keystr.equals("k")) return KeyEvent.VK_K;
    if (keystr.equals("l")) return KeyEvent.VK_L;
    if (keystr.equals("m")) return KeyEvent.VK_M;
    if (keystr.equals("n")) return KeyEvent.VK_N;
    if (keystr.equals("o")) return KeyEvent.VK_O;
    if (keystr.equals("p")) return KeyEvent.VK_P;
    if (keystr.equals("q")) return KeyEvent.VK_Q;
    if (keystr.equals("r")) return KeyEvent.VK_R;
    if (keystr.equals("s")) return KeyEvent.VK_S;
    if (keystr.equals("t")) return KeyEvent.VK_T;
    if (keystr.equals("u")) return KeyEvent.VK_U;
    if (keystr.equals("v")) return KeyEvent.VK_V;
    if (keystr.equals("w")) return KeyEvent.VK_W;
    if (keystr.equals("x")) return KeyEvent.VK_X;
    if (keystr.equals("y")) return KeyEvent.VK_Y;
    if (keystr.equals("z")) return KeyEvent.VK_Z;
    if (keystr.equals("f1")) return KeyEvent.VK_F1;
    if (keystr.equals("f2")) return KeyEvent.VK_F2;
    if (keystr.equals("f3")) return KeyEvent.VK_F3;
    if (keystr.equals("f4")) return KeyEvent.VK_F4;
    if (keystr.equals("f5")) return KeyEvent.VK_F5;
    if (keystr.equals("f6")) return KeyEvent.VK_F6;
    if (keystr.equals("f7")) return KeyEvent.VK_F7;
    if (keystr.equals("f8")) return KeyEvent.VK_F8;
    if (keystr.equals("f9")) return KeyEvent.VK_F9;
    if (keystr.equals("f10")) return KeyEvent.VK_F10;
    if (keystr.equals("f11")) return KeyEvent.VK_F11;
    if (keystr.equals("f12")) return KeyEvent.VK_F12;
    if (keystr.equals("f13")) return KeyEvent.VK_F13;
    if (keystr.equals("f14")) return KeyEvent.VK_F14;
    if (keystr.equals("f15")) return KeyEvent.VK_F15;
    if (keystr.equals("f16")) return KeyEvent.VK_F16;
    if (keystr.equals("f17")) return KeyEvent.VK_F17;
    if (keystr.equals("f18")) return KeyEvent.VK_F18;
    if (keystr.equals("f19")) return KeyEvent.VK_F19;
    if (keystr.equals("esc")) return KeyEvent.VK_ESCAPE;
    if (keystr.equals("space")) return KeyEvent.VK_SPACE;
    if (keystr.equals("enter")) return KeyEvent.VK_ENTER;
    if (keystr.equals("tab")) return KeyEvent.VK_TAB;
    if (keystr.equals("backspace")) return KeyEvent.VK_BACK_SPACE;
    if (keystr.equals("delete")) return KeyEvent.VK_DELETE;
    if (keystr.equals("caps")) return KeyEvent.VK_CAPS_LOCK;
    if (keystr.equals("down")) return KeyEvent.VK_DOWN;
    if (keystr.equals("up")) return KeyEvent.VK_UP;
    if (keystr.equals("left")) return KeyEvent.VK_LEFT;
    if (keystr.equals("right")) return KeyEvent.VK_RIGHT;
    if (keystr.equals(",")) return KeyEvent.VK_COMMA;
    if (keystr.equals(".")) return KeyEvent.VK_PERIOD;
    if (keystr.equals("+")) return KeyEvent.VK_PLUS;
    if (keystr.equals("-")) return KeyEvent.VK_MINUS;
    return 0;
  }
  
  void send() {
    for (int i = 0; i < keys.length; i++) {
      if (keys[i] != 0) {
        debug("Sending keyPress "+keys[i]);
        robot.keyPress(keys[i]);
      }
    }
  }

  void sendOff() {
    for (int i = 0; i < keys.length; i++) {
      if (keys[i] != 0) {
        robot.keyRelease(keys[i]);
      }
    }
  }

  void down() {
    send();
    on();
  }
  
  void up() { //<>//
    sendOff();
    off();
  }
  
  void update() {
    if (value == 0) {
      off();
    }
    if (value == 127) {
      on();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    value = ivalue;
    if (ivalue == 0) { 
      sendoff = true;    
    }
    propagateToChainedControls();
  }
}

class FaderSegment extends GridSegment {
  Fader owner;
  
  FaderSegment(int ix, int iy, Fader iowner) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    owner = iowner;
    controlID = (int)random(1000);
    grid[y*8+x] = this;
    idlecolor = FADERIDLECOLOR;
    activecolor = FADERACTIVECOLOR;
    off();
  }
  
  void down() {
    owner.faderAction(this);
  }
  
  void update() {
    if (x == owner.x && y == owner.y) {
      owner.update();
    }
  }
  
  void send() {
    if (x == owner.x && y == owner.y) {
      owner.send();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (x == owner.x && y == owner.y) {
      owner.nakedSetValue(ivalue);
    }
  }
  
  void chainTo(Control control, boolean send) {
    owner.chainTo(control, send);
  }
  
  void propagateToChainedControls() {
    owner.propagateToChainedControls();
  }
}

class Fader extends Control {
  FaderSegment[] segments;
  long lastupdate;
  boolean schedule;
  
  
  void faderAction(FaderSegment sender){}
  
  boolean canUpdate() {
    long now = (new Date()).getTime();
    debug("l "+lastupdate);
    debug("n "+now);
    debug("d "+(now-lastupdate));
    if (now - lastupdate < 100) {
      lastupdate = now;
      return true;
    } else {
     return true;
    } 
  }
  
  void schedule() {
    schedule = true;
    new Scheduler(this);
  }
  
  void cancelSchedule() {
    schedule = false;
  }
  
  boolean hasSchedule() {
    return schedule;
  }
  
}

class XFader extends Fader {
  int sx;
  int lastsx;

  XFader(int ix, int iy, int ixsize) {
    page = selectedPage;
    println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    sx = x;
    lastsx = -1;
    takeover = 0;
    segments = new FaderSegment[xsize];
    for (int i = 0; i < xsize; i++) {
      segments[i] = new FaderSegment(x+i, y, this);
    }
    grid[y*8+x].on();
  }
  
  void faderAction(FaderSegment sender) {
    sx = sender.x;
    float relx = sx - x;
    float quantum = relx * (1 / ((float)xsize - 1));
    float amount = ((relx + quantum) / (float)xsize) * maxvalue;
    setValue((int)amount);
  }
  
  void update() {
    if (lastsx == sx && !hasSchedule()) schedule();
    if (lastsx != sx || canUpdate()) {
      if (page == selectedPage) {    
        for(int i = 0; i < xsize; i++) {
          if (i <= sx) { grid[y*8+x+i].on(); } else { grid[y*8+x+i].off(); }
        }
        lastsx = sx;
        lastupdate = (new Date()).getTime();
      }
    }
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    controlOut(x, y, value);
  }
  
  void setTakeover(int itakeover) {
    //debug("setTakeover()");
    takeover = itakeover;
  }
  
  void nakedSetValue(int ivalue) {
    if (TAKEOVER == false) {
      float step = 127 / (xsize-1);
      sx = (int)(ivalue / step);
      value = ivalue;
      propagateToChainedControls();
    } else {
      //debug("nakedSetValueX() blocked because takeover is in progress.");
    }
  }
  
  void takeoverSetValue(int ivalue) {
    float step = maxvalue / (xsize-1);
    sx = (int)(ivalue / step);
    value = ivalue;
    send();
    if (lastsx != sx) update();
    propagateToChainedControls();
  }
  
  void setValue(int ivalue) {
    if (takeover == 0) {
      float step = maxvalue / (xsize-1);
      sx = (int)(ivalue / step);
      value = ivalue;
      send();
      if (lastsx != sx) update();
      propagateToChainedControls();
    } else {
      //debug("Delegating control to takeover routine");
      takeover(ivalue);
    }    
  }
  
  void takeover(int ivalue) {
    threadsSpawned++;
    TakeOver takeover = new TakeOver(this, ivalue, this.takeover);
  }
  void setMaxValue(int imaxvalue) {
     debug("setMaxValue");
     maxvalue = imaxvalue;
   }
}

class IXFader extends Fader {
  int sx;
  int lastsx;

  IXFader(int ix, int iy, int ixsize) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    sx = 0;
    lastsx = -1;
    takeover = 0;
    segments = new FaderSegment[xsize];
    for (int i = 0; i < xsize; i++) {
      segments[i] = new FaderSegment(x+i, y, this);
    }
    grid[y*8+x].on();
  }
  
  void faderAction(FaderSegment sender) {
    sx = sender.x;
    float relx = sx - x;
    float quantum = relx * (1 / ((float)xsize - 1));
     float amount = ((relx + quantum) / (float)xsize) * maxvalue;
    setValue(maxvalue-(int)amount);
  }
  
  void update() {
    if (lastsx == sx && !hasSchedule()) schedule();
    if (lastsx != sx || canUpdate()) {
      if (page == selectedPage) {    
        for(int i = xsize-1; i >= 0; i--) {
          debug("sx="+((xsize-1)-sx)+" i="+i);
          if (i >= (xsize-1)-sx) { grid[y*8+x+i].on(); } else { grid[y*8+x+i].off(); }
          if (i <= sx) { grid[y*8+x+i].on(); } else { grid[y*8+x+i].off(); }
        }
        lastsx = sx;
        lastupdate = (new Date()).getTime();
      }
    }
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    controlOut(x, y, value);
  }
  
  void setTakeover(int itakeover) {
    //debug("setTakeover()");
    takeover = itakeover;
  }
  
  void nakedSetValue(int ivalue) {
    if (TAKEOVER == false) {
        float step = maxvalue / (xsize-1);
      sx = (int)(ivalue / step);
      value = ivalue;
      propagateToChainedControls();
    } else {
      //debug("nakedSetValueX() blocked because takeover is in progress.");
    }
  }
  
  void takeoverSetValue(int ivalue) {
      float step = maxvalue / (xsize-1);
    sx = (int)(ivalue / step);
    value = ivalue;
    send();
    if (lastsx != sx) update();
    propagateToChainedControls();
  }
  
  void setValue(int ivalue) {
    if (takeover == 0) {
      float step = 127 / (xsize-1);
      sx = (int)(ivalue / step);
      value = ivalue;
      send();
      if (lastsx != sx) update();
      propagateToChainedControls();
    } else {
      //debug("Delegating control to takeover routine");
      takeover(ivalue);
    }    
  }
  
  void takeover(int ivalue) {
    threadsSpawned++;
    TakeOver takeover = new TakeOver(this, ivalue, this.takeover);
  }
        
}

class YFader extends Fader {
  int sy;
  int lastsy;
  
  YFader(int ix, int iy, int iysize) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    ysize = iysize;
    sy = y;
    lastsy = -1;
    value = 0;
    segments = new FaderSegment[ysize];
    for (int i = 0; i < ysize; i++) {
      segments[i] = new FaderSegment(x, y - i, this);
    }
    grid[y*8+x].on();
  }
  
  void faderAction(FaderSegment sender) {
    sy = sender.y;
    float rely = sy - y;
    float quantum = rely * (1 / ((float)ysize - 1));
    float amount = ((rely + quantum) / (float)ysize) * maxvalue;
    setValue(0 - ((int)amount));
  }
  
  void update() {
    //if (lastsy == sy && !hasSchedule()) schedule();
    if (lastsy != sy || canUpdate()) {

    if (page == selectedPage) {
      for(int i = y; i > y - ysize; i--) {
        if (i >= sy) { grid[i*8+x].on(); } else { grid[i*8+x].off(); }
      }
      lastsy = sy;
      lastupdate = (new Date()).getTime();
    }
    }
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    controlOut(x, y, value);
  }
  
  void setTakeover(int itakeover) {
    //debug("setTakeover()");
    takeover = itakeover;
  }
  
  void nakedSetValue(int ivalue) {
    if (TAKEOVER == false) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      value = ivalue;
      propagateToChainedControls();
    } else {
      //debug("nakedSetValue() blocked because takeover is in progress.");
    }
  }

  void takeoverSetValue(int ivalue) {
    float step = 127 / (ysize-1);
    int rely = ((int)(ivalue / step));
    sy = y - rely;
    value = ivalue;
    send();
    //debug("sy "+sy+" lastsy "+lastsy);
    if (lastsy != sy) update();
    propagateToChainedControls();
  }
  
  void setValue(int ivalue) {
    if (takeover == 0) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      value = ivalue;
      send();
      if (lastsy != sy) update();
      propagateToChainedControls();
    } else {
      //debug("Delegating control to takeover routine");
      takeover(ivalue);
    }
  }
  
  void takeover(int ivalue) {
    threadsSpawned++;
    TakeOver takeover = new TakeOver(this, ivalue, this.takeover);
  }
}

class IYFader extends Fader {
  int sy;
  int lastsy;
  
  IYFader(int ix, int iy, int iysize) {
    controlID = (int)random(1000);
    page = selectedPage;
    println(this+" on page "+page);
    x = ix;
    y = iy;
    ysize = iysize;
    sy = y;
    lastsy = -1;
    value = 0;
    segments = new FaderSegment[ysize];
    for (int i = 0; i < ysize; i++) {
      segments[i] = new FaderSegment(x, y - i, this);
    }
    grid[y*8+x].on();
  }
  
  void faderAction(FaderSegment sender) {
    sy = sender.y;
    debug("!! sy="+sy);
    float rely = sy - y;
    float quantum = rely * (1 / ((float)ysize - 1));
    float amount = ((rely + quantum) / (float)ysize) * 127;
    setValue(127 + ((int)amount));
    debug("!! value="+(127 + ((int)amount)));
  }
  
  void update() {
    if (lastsy != sy || canUpdate()) {

    if (page == selectedPage) {
      for(int i = 0; i < ysize; i++) {
        int pos = y - sy;
        pos = (ysize-1)-pos;
        debug("i="+i+" sy="+sy+" pos="+pos);
        if (i >= pos ) { grid[(y-i)*8+x].on(); } else { grid[(y-i)*8+x].off(); }
      }
      
      lastsy = sy;
      lastupdate = (new Date()).getTime();
    }
    }
  }
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    controlOut(x, y, value);
  }
  
  void setTakeover(int itakeover) {
    //debug("setTakeover()");
    takeover = itakeover;
  }
  
  void nakedSetValue(int ivalue) {
    if (TAKEOVER == false) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      value = ivalue;
      propagateToChainedControls();
    } else {
      //debug("nakedSetValue() blocked because takeover is in progress.");
    }
  }

  void takeoverSetValue(int ivalue) {
    float step = 127 / (ysize-1);
    int rely = ((int)(ivalue / step));
    sy = y - rely;
    value = ivalue;
    send();
    //debug("sy "+sy+" lastsy "+lastsy);
    if (lastsy != sy) update();
    propagateToChainedControls();
  }
  
  void setValue(int ivalue) {
    if (takeover == 0) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      value = ivalue;
      send();
      if (lastsy != sy) update();
      propagateToChainedControls();
    } else {
      //debug("Delegating control to takeover routine");
      takeover(ivalue);
    }
  }
  
  void takeover(int ivalue) {
    threadsSpawned++;
    TakeOver takeover = new TakeOver(this, ivalue, this.takeover);
  }
}

class SliderSegment extends GridSegment {
  Slider owner;
  
  SliderSegment(int ix, int iy, Slider iowner) {
    page = selectedPage;
    println(this+" on page "+page);
    x = ix;
    y = iy;
    owner = iowner;
    grid[y*8+x] = this;
    idlecolor = SLIDERIDLECOLOR;
    activecolor = SLIDERACTIVECOLOR;
    off();
  }
  
  void down() {
    owner.sliderAction(this);
  }
  
  void up() {
    owner.liftFinger(this);
  }
  
  void update() {
    off();
  }
  
  void send() {
    if (x == owner.x && y == owner.y) {
      owner.send();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (x == owner.x && y == owner.y) {
      owner.setValue(ivalue);
    }
  }
  
  void chainTo(Control control, boolean send) {
    owner.chainTo(control, send);
  }
  
  void propagateToChainedControls() {
    owner.propagateToChainedControls();
  }
}

class Slider extends Control {
  SliderSegment[] segments;
  
  void sliderAction(SliderSegment sender){}
  void liftFinger(SliderSegment sender){};
  
  void send() {
    if (channel != 0) outputChannel = channel-1;
    controlOut(x, y, value);
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    value = ivalue;
    propagateToChainedControls();
  }
}

class XSlider extends Slider {
  int lastSegment;
  int segment;
  int granularity;
  
  XSlider(int ix, int iy, int ixsize, int igranularity) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    value = SLIDERDEFAULTVALUE;
    granularity = igranularity;
    segments = new SliderSegment[xsize];
    for (int i = 0; i < xsize; i++) {
      segments[i] = new SliderSegment(x+i, y, this);
    }
  }
  
  void sliderAction(SliderSegment sender) {
    segment = sender.x;
    if (lastSegment - 1 == segment) {
      decrease();
      //debug("Decreasing "+this+" to "+value);
    }
    if (lastSegment + 1 == segment) {
      increase();
      //debug("Increasing "+this+" to "+value);
    }
    sender.on();
    lastSegment = segment;
  }
  
  void liftFinger(SliderSegment sender) {
    sender.off();
  }
  
  void increase() {
    if (value < 127) {
      value += granularity;
    }
    if (value > 127) {
      value = 127;
    }
    send();
    propagateToChainedControls();
  }
  
  void decrease() {
    if (value > 0) {
      value -= granularity;
    }
    if (value < 0) {
      value = 0;
    }
    send();
    propagateToChainedControls();
  }
}

class YSlider extends Slider {
  int lastSegment;
  int segment;
  int granularity;
  
  YSlider(int ix, int iy, int iysize, int igranularity) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    ysize = iysize;
    value = SLIDERDEFAULTVALUE;
    granularity = igranularity;
    segments = new SliderSegment[ysize];
    for (int i = 0; i < ysize; i++) {
      segments[i] = new SliderSegment(x, y - i, this);
    }
  }
  
  void sliderAction(SliderSegment sender) {
    segment = sender.y;
    if (lastSegment - 1 == segment) {
      increase();
    }
    if (lastSegment + 1 == segment) {
      decrease();
    }
    sender.on();
    lastSegment = segment;
  }
  
  void liftFinger(SliderSegment sender) {
    sender.off();
  }
  
  void increase() {
    if (value < 127) {
      value += granularity;
    }
    if (value > 127) {
      value = 127;
    }
    send();
    propagateToChainedControls();
  }
  
  void decrease() {
    if (value > 0) {
      value -= granularity;
    }
    if (value < 0) {
      value = 0;
    }
    send();
    propagateToChainedControls();
  }
}

class PadSegment extends GridSegment {
  Pad owner;
  
  PadSegment(int ix, int iy, Pad iowner) {
    x = ix;
    y = iy;
    owner = iowner;
    grid[y*8+x] = this;
    idlecolor = PADOFFCOLOR;
    activecolor = PADONCOLOR;
    off();
  }
  
  void down() {
    owner.padAction(this);
  }
  
  void update() {
    owner.update(this);
  }
  
  void send() {
    if (x == owner.x && y == owner.y) {
      owner.sendX();
    }
    if (x == owner.x+1 && y == owner.y) {
      owner.sendY();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (x == owner.x && y == owner.y) {
      owner.nakedSetValueX(ivalue);
    }
    if (x == owner.x+1 && y == owner.y) {
      owner.nakedSetValueY(ivalue);
    }
  }
  
  void chainTo(Control control, boolean send) {
    owner.chainTo(control, send);
  }
  
  void propagateToChainedControls() {
    owner.propagateToChainedControls();
  }
}

class Pad extends Control {
  PadSegment[] padSegments;
  int sx;
  int sy;
  int lastsx;
  int lastsy;
  int xvalue;
  int yvalue;
  boolean invertx = false;
  boolean inverty = false;
  boolean TAKEOVER;
  long threadsSpawned = 0;
  long threadsFinished = 0;
  
  Pad(int ix, int iy, int ixsize, int iysize) {
    page = selectedPage;
    println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    ysize = iysize;
    sx = x;
    sy = y+ysize-1;
    takeover = 0;
    yvalue = 0;
    xvalue = 0;
    lastsx = x;
    lastsy = y+ysize-1;
    padSegments = new PadSegment[xsize*ysize];
    
    for (int yi = 0; yi < ysize; yi++) {
      for (int xi = 0; xi < xsize; xi++) {
        padSegments[yi*ysize+xi] = new PadSegment(xi+x, yi+y, this);
      }
    }
  }
  
  void padAction(PadSegment sender) {
    //debug("padAction()");
    sy = sender.y;
    float rely = sy - y;
    float quantum = rely * (1 / ((float)ysize - 1));
    float amounty = 127-((rely + quantum) / (float)ysize) * 127;
    sx = sender.x;
    float relx = sx - x;
    quantum = relx * (1 / ((float)xsize - 1));
    float amountx = ((relx + quantum) / (float)xsize) * 127;
    setValue((int)amountx, (int)amounty);
  }
  
  void send() {
    sendX();
    sendY();
  }
  
  void sendX() {
    if (channel != 0) outputChannel = channel-1;
    if (!invertx) controlOut(x, y, xvalue);
    if (invertx) controlOut(x, y, 127-xvalue);
  }
  
  void sendY() {
    if (channel != 0) outputChannel = channel-1;
    if (!inverty) controlOut(x+1, y, yvalue);
    if (inverty) controlOut(x+1, y, 127-yvalue);

  }
  
  void setTakeover(int takeover) {
    this.takeover = takeover;
  }
  
  void invertx() {
    invertx = true;
  }
  
  void inverty() {
    inverty = true;
  }
  
  void nakedSetValueX(int ivalue) {
    if (invertx) ivalue = 127-ivalue;
    if (TAKEOVER == false) {
      float step = 127 / (xsize-1);
      sx = x+(int)(ivalue / step);
      xvalue = ivalue;
      propagateToChainedControls();
      //debug("nakedSetValueX() succeeded.");
    } else {
      //debug("nakedSetValueX() blocked because takeover is in progress.");
    }
  }
  
  void nakedSetValueY(int ivalue) {
    if (inverty) ivalue = 127-ivalue;
    if (TAKEOVER == false) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = (ysize-1)+(y - rely);
      yvalue = ivalue;
      propagateToChainedControls();
      //debug("nakedSetValueY() succeeded.");
    } else {
      //debug("nakedSetValueY() blocked because takeover is in progress.");
    }
  }
  
  void takeoverSetValue(int ixvalue, int iyvalue) {
    float step = 127 / (ysize-1);
      int rely = ((int)(iyvalue / step));
      sy = (ysize-1)+(y - rely);
      yvalue = iyvalue;
      
      step = 127 / (xsize-1);
      sx = x+(int)(ixvalue / step);
      xvalue = ixvalue;
      //debug("xvalue = "+xvalue+". sx = "+sx);
      //debug("yvalue = "+yvalue+". sy = "+sy);
      send();
      if (lastsy != sy || lastsx != sx) update();
      propagateToChainedControls();
  }
  
  void setValue(int ixvalue, int iyvalue) {
    if (takeover == 0) {
      float step = 127 / (ysize-1);
      int rely = ((int)(iyvalue / step));
      sy = (ysize-1)+(y - rely);
      yvalue = iyvalue;
      
      step = 127 / (xsize-1);
      sx = x+(int)(ixvalue / step);
      xvalue = ixvalue;
      //debug("xvalue = "+xvalue+". sx = "+sx);
      //debug("yvalue = "+yvalue+". sy = "+sy);
      send();
      if (lastsy != sy || lastsx != sx) update();
      propagateToChainedControls();
    } else {
      //debug("Delegating control to takeover routine");
      takeover(ixvalue, iyvalue);
    }
  }
  
  void takeover(int ixvalue, int iyvalue) {
      threadsSpawned++;
      TakeOver2d takeover = new TakeOver2d(this, ixvalue, iyvalue, this.takeover);
  }
  
  void update() {
    if (page == selectedPage) {
      //debug("update() sx="+sx+" sy="+sy);
      grid[lastsx+lastsy*8].off();
      grid[sx+sy*8].on();
      lastsx = sx;
      lastsy = sy;
    }
  }
  
  void update(PadSegment sender) {
    //debug("update("+sender+") sx="+sx+" sy="+sy);
    if ((sender.x == x && sender.y == y) || (sender.x-1 == x && sender.y == y)) {
      //debug("is the case");
      if (sender.x == sx && sender.y == sy) { sender.on(); } else { sender.off(); }
      grid[sx+sy*8].on();
      if (lastsx != sx || lastsy != sy) {
        grid[lastsx+lastsy*8].off();
        lastsx = sx;
        lastsy = sy;
      }
      
    } else {
      //debug("not the case");
      if (sender.x == sx && sender.y == sy) sender.on();
      if (sender.x != sx || sender.y != sy) sender.off();
    }
    /*lastsx = sx;
    lastsy = sy;*/
    
  }
}

class Drumrack extends Control {
  
  Drumrack(int ix, int iy, int ixsize, int iysize, int startOctave, String startNote, int vel, boolean invert) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    ysize = iysize;
    Note note;
    
    int snote = addOctave(parseNote(startNote), startOctave);
    
    if (invert) {
      for (int yi = 0; yi < ysize; yi++) {
        for (int xi = 0; xi < xsize; xi++) {
          new Note(xi+x, ((ysize-1)-yi)+y, snote, vel);
          snote++;
        }
      }
    } else {
      for (int yi = 0; yi < ysize; yi++) {
        for (int xi = 0; xi < xsize; xi++) {
          new Note(xi+x, yi+y, snote, vel);
          snote++;
        }
      }
    }
  }
  
  int parseNote(String note) {
    note = note.toLowerCase();
    if (note.equals("c")) return 0;
    if (note.equals("c#")) return 1;
    if (note.equals("d")) return 2;
    if (note.equals("d#")) return 3;
    if (note.equals("e")) return 4;
    if (note.equals("f")) return 5;
    if (note.equals("f#")) return 6;
    if (note.equals("g")) return 7;
    if (note.equals("g#")) return 8;
    if (note.equals("a")) return 9;
    if (note.equals("a#")) return 10;
    if (note.equals("b")) return 11;
    return 0;
  }
  
  int addOctave(int note, int octave) {
    octave = octave+1;
    return note+(octave*12);
  }
  
}

class MeterSegment extends GridSegment {
  Meter owner;
  
  MeterSegment(int ix, int iy, Meter iowner) {
    page = selectedPage;
    x = ix;
    y = iy;
    owner = iowner;
    controlID = (int)random(1000);
    grid[y*8+x] = this;
    idlecolor = METERIDLECOLOR;
    activecolor = METERACTIVECOLOR;
    off();
  }
  
  void update() {
    if (x == owner.x && y == owner.y) {
      owner.update();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (x == owner.x && y == owner.y) {
      owner.nakedSetValue(ivalue);
    }
  }
  
  void chainTo(Control control, boolean send) {
    owner.chainTo(control, send);
  }
  
  void propagateToChainedControls() {
    owner.propagateToChainedControls();
  }
}

class Meter extends Control {
  MeterSegment[] segments;
  long lastupdate;
  
  boolean canUpdate() {
    long now = (new Date()).getTime();
    if (now - lastupdate > updatedelay) {
      lastupdate = now;
     return true;
    } else {
     return false;
    } 
  }
}

class XMeter extends Meter {
  int sx;
  float sxf;
  int lastsx;
  float lastsxf;

  XMeter(int ix, int iy, int ixsize) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    sx = x;
    lastsx = -1;
    takeover = 0;
    segments = new MeterSegment[xsize];
    for (int i = 0; i < xsize; i++) {
      segments[i] = new MeterSegment(x+i, y, this);
    }
    grid[y*8+x].on();
  }
    
  void update() {
    
    if (canUpdate()) {
      //debug("go");
    if (page == selectedPage) { 
      for(int i = 0; i < xsize; i++) {
        
        if (i != sx && i != sx+1) {
          grid[y*8+x+i].off();
        }
        
        if (i == sx+1 && !(sxf - sx >= 0.56) && !(((sxf - sx) > 0.43 && (sxf - sx) < 0.56) && sx != xsize-1)) {
          grid[y*8+x+i].off();
        }
        
        if (i == sx) { 
          if (sxf - sx <= 0.43) {
            grid[y*8+x+i].on();
          }
          
          if (sxf - sx >= 0.56) {
            grid[y*8+x+(i+1)].on();
          }
          
          if (((sxf - sx) > 0.43 && (sxf - sx) < 0.56) && sx != xsize-1) {
            grid[y*8+x+i].on();
            grid[y*8+x+i+1].on();
          }
        }
        
      }
      lastsx = sx;
      lastsxf = sxf;
    }
    } else { /*debug("filtered");*/ }
  }
  
  
  void nakedSetValue(int ivalue) {
      float step = 127 / (xsize-1);
      sxf = (ivalue / step);
      //println(sxf);
      sx = (int)sxf;
      value = ivalue;
      propagateToChainedControls();
  }
  
  
  void setValue(int ivalue) {
      float step = 127 / (xsize-1);
      sxf = (ivalue / step);
      //println(sxf);
      sx = (int)sxf;
      value = ivalue;
      send();
      if (lastsxf != sxf) update();
      propagateToChainedControls();
   
  }
        
}

class YMeter extends Meter {
  int sy;
  int lastsy;
  float syf;
  float lastsyf;
  
  YMeter(int ix, int iy, int iysize) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    ysize = iysize;
    sy = y;
    lastsy = -1;
    value = 0;
    segments = new MeterSegment[ysize];
    for (int i = 0; i < ysize; i++) {
      segments[i] = new MeterSegment(x, y - i, this);
    }
    grid[y*8+x].on();
  }
  
  void update() {
    //debug("diff: "+abs((lastsy-lastsyf) - (sy-syf)));

    if (page == selectedPage && canUpdate()) { 
      for(int i = y; i >= y-(ysize-1); i--) {
        if (i != sy && i != sy-1) {
          grid[i*8+x].off();
          //debug("turning off 0");
        }
        
        if (i == sy-1 && !(sy - syf >= 0.56) && !(((sy - syf) > 0.43 && (syf - sy) < 0.56))) {
          grid[(i)*8+x].off();
        }
        
        
        if (i == sy) { 
          if (sy - syf <= 0.43) {
            grid[i*8+x].on();
            //debug("turning on 0");
          }
          
          if (sy - syf >= 0.56) {
            grid[(i-1)*8+x].on();
            //debug("turning on -1");
          }
          
          if (((sy - syf) > 0.43 && (syf - sy) < 0.56) && sy != y-ysize-1) {
            grid[i*8+x].on();
            grid[(i-1)*8+x].on();
            //debug("turning on both");
          }
        }
        
      }
      
      lastsy = sy;
      lastsyf = syf;
    }

  }
  
  
  
  void nakedSetValue(int ivalue) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      syf = y - abs((ivalue / step));
      sy = y - rely;
      //debug("syf: "+syf);
      value = ivalue;
      propagateToChainedControls();
  }
  
  void setValue(int ivalue) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      syf = y - abs((ivalue / step));
      //debug("syf: "+syf);
      value = ivalue;
      send();
      if (lastsyf != syf) update();
      propagateToChainedControls();
  }
}

class ProgressSegment extends GridSegment {
  Progress owner;
  
  ProgressSegment(int ix, int iy, Progress iowner) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    owner = iowner;
    controlID = (int)random(1000);
    grid[y*8+x] = this;
    idlecolor = PROGRESSIDLECOLOR;
    activecolor = PROGRESSACTIVECOLOR;
    off();
  }
  
  
  void update() {
    if (x == owner.x && y == owner.y) {
      owner.update();
    }
  }
  

  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (x == owner.x && y == owner.y) {
      owner.nakedSetValue(ivalue);
    }
  }
  
  void chainTo(Control control, boolean send) {
    owner.chainTo(control, send);
  }
  
  void propagateToChainedControls() {
    owner.propagateToChainedControls();
  }
}

class Progress extends Control {
  ProgressSegment[] segments;
  long lastupdate;
  
  boolean canUpdate() {
    long now = (new Date()).getTime();
    if (now - lastupdate > updatedelay) {
      lastupdate = now;
     return true;
    } else {
     return false;
    } 
  }
}

class XProgress extends Progress {
  int sx;
  int lastsx;

  XProgress(int ix, int iy, int ixsize) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    sx = x;
    lastsx = -1;
    takeover = 0;
    segments = new ProgressSegment[xsize];
    for (int i = 0; i < xsize; i++) {
      segments[i] = new ProgressSegment(x+i, y, this);
    }
    grid[y*8+x].on();
  }

  void update() {
    //if (lastsx != sx) {
    if (page == selectedPage && canUpdate()) {    
      for(int i = 0; i < xsize; i++) {
        if (i <= sx) { grid[y*8+x+i].on(); } else { grid[y*8+x+i].off(); }
      }
      lastsx = sx;
    }
    //}
  }
  
  void nakedSetValue(int ivalue) {
      float step = 127 / (xsize-1);
      sx = (int)(ivalue / step);
      value = ivalue;
      propagateToChainedControls();
  }
  
  
  void setValue(int ivalue) {
      float step = 127 / (xsize-1);
      sx = (int)(ivalue / step);
      value = ivalue;
      send();
      if (lastsx != sx) update();
      propagateToChainedControls();    
  }   
}

class YProgress extends Progress {
  int sy;
  int lastsy;
  
  YProgress(int ix, int iy, int iysize) {
    controlID = (int)random(1000);
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    ysize = iysize;
    sy = y;
    lastsy = -1;
    value = 0;
    segments = new ProgressSegment[ysize];
    for (int i = 0; i < ysize; i++) {
      segments[i] = new ProgressSegment(x, y - i, this);
    }
    grid[y*8+x].on();
  }
  
  void update() {
    //if (lastsy != sy) {
    if (page == selectedPage && canUpdate()) {
      for(int i = y; i > y - ysize; i--) {
        if (i >= sy) { grid[i*8+x].on(); } else { grid[i*8+x].off(); }
      }
      lastsy = sy;
    }
    //}
  }
  
  void nakedSetValue(int ivalue) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      value = ivalue;
      propagateToChainedControls();
  }
  
  void setValue(int ivalue) {
      float step = 127 / (ysize-1);
      int rely = ((int)(ivalue / step));
      sy = y - rely;
      value = ivalue;
      send();
      if (lastsy != sy) update();
      propagateToChainedControls();

  }
}

class CrsFaderSegment extends GridSegment {
  CrsFader owner;
  
  CrsFaderSegment(int ix, int iy, CrsFader iowner) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    owner = iowner;
    controlID = (int)random(1000);
    grid[y*8+x] = this;
    idlecolor = FADERIDLECOLOR;
    activecolor = FADERACTIVECOLOR;
    off();
  }
  
  void down() {
    owner.faderAction(this);
  }
  
  void up() {
    owner.stopAction();
  }
  
  void update() {
    if (x == owner.x && y == owner.y) {
      owner.update();
    }
  }
  
  void send() {
    if (x == owner.x && y == owner.y) {
      owner.send();
    }
  }
  
  void nakedSetValue(int ivalue) { setValue(ivalue); }
  void setValue(int ivalue) {
    if (x == owner.x && y == owner.y) {
      owner.nakedSetValue(ivalue);
    }
  }
  
  void chainTo(Control control, boolean send) {
    owner.chainTo(control, send);
  }
  
  void propagateToChainedControls() {
    owner.propagateToChainedControls();
  }
}

class CrsFader extends Control {
  int sx;
  float sxf;
  int lastsx;
  float lastsxf;
  float factor;
  boolean running;
  CrsFaderSegment[] segments;
  long lastupdate;
  
  CrsFader(int ix, int iy, int ixsize) {
    page = selectedPage;
    //println(this+" on page "+page);
    x = ix;
    y = iy;
    xsize = ixsize;
    sx = x;
    lastsx = -1;
    takeover = 0;
    factor = 0;
    running = false;
    segments = new CrsFaderSegment[xsize];
    for (int i = 0; i < xsize; i++) {
      segments[i] = new CrsFaderSegment(x+i, y, this);
    }
    grid[y*8+x].on();
  }
  
  void stopAction() {
    running = false;
    factor = 0;
  }
  
  void faderAction(CrsFaderSegment sender) {
    if (xsize % 2 != 0) {
      int relx = sender.x - x + 1;
      int center = abs(round(((float)xsize/2)));
      float relpos = ((relx - center)/((float)xsize-1))*2;
      
      debug("CrsFader update. Sender x = "+sender.x+", relx="+relx);
      debug("Relative position: "+relpos+", center="+center);
    } else {
      int relx = sender.x - x + 1;
      int center = xsize/2;
      float relpos = 0;
      
      if (relx <= center) {
        relpos = (center - (relx-1))/(float)center * -1;
      } else {
        relpos = (relx - center)/(float)center;
      }
      
      debug("CrsFader update. Sender x = "+sender.x+", relx="+relx);
      debug("Relative position: "+relpos+", center="+center);
      
      factor = relpos;
      if (factor != 0 && !running) {
        running = true;
        spawnThread();
      }
    }
  }
  
  void spawnThread() {
    CrsUpdater updater = new CrsUpdater(this);
  }
  
  void updateValue() {
    int nvalue = (int)(value+(factor*6));
    if (nvalue < 0) nvalue = 0;
    if (nvalue > 127) nvalue = 127;
    setValue(nvalue);
  }
  
  boolean canUpdate() {
    long now = (new Date()).getTime();
    if (now - lastupdate > updatedelay) {
      lastupdate = now;
     return true;
    } else {
     return true;
    } 
  }
    
  void update() {
    
    if (canUpdate()) {
      //debug("go");
    if (page == selectedPage) { 
      for(int i = 0; i < xsize; i++) {
        
        if (i != sx && i != sx+1) {
          grid[y*8+x+i].off();
        }
        
        if (i == sx+1 && !(sxf - sx >= 0.56) && !(((sxf - sx) > 0.43 && (sxf - sx) < 0.56) && sx != xsize-1)) {
          grid[y*8+x+i].off();
        }
        
        if (i == sx) { 
          if (sxf - sx <= 0.43) {
            grid[y*8+x+i].on();
          }
          
          if (sxf - sx >= 0.56) {
            grid[y*8+x+(i+1)].on();
          }
          
          if (((sxf - sx) > 0.43 && (sxf - sx) < 0.56) && sx != xsize-1) {
            grid[y*8+x+i].on();
            grid[y*8+x+i+1].on();
          }
        }
        
      }
      lastsx = sx;
      lastsxf = sxf;
    }
    } else { /*debug("filtered");*/ }
  }
  
  
  void nakedSetValue(int ivalue) {
    if (!running) {
      float step = 127 / (xsize-1);
      sxf = (ivalue / step);
      //println(sxf);
      sx = (int)sxf;
      value = ivalue;
      propagateToChainedControls();
    } else {
      debug("Blocked because thread is running");
    }
  }
  
  
  void setValue(int ivalue) {
      float step = 127 / (xsize-1);
      sxf = (ivalue / step);
      //println(sxf);
      sx = (int)sxf;
      value = ivalue;
      send();
      if (lastsxf != sxf) update();
      propagateToChainedControls();
  }
  
  void send() {
    
    controlOut(x, y, value);
  }
        
}