
// ----------------------------------------------------------------------

void appendOffset() {
  // this is called when Dither is active to append the offset to the
  // current servo tyarget
  if (comFlag < 1) {return;}  // don't do anything if not connected

  int zSV;
  setSliderX();

  zSV = servoVal[servoTgt] + DitherOffset;
  zSV = max(zSV,servoVLL[servoTgt]); zSV = min(zSV,servoVUL[servoTgt]);
  msgTx = "ST" + str(servoTgt) + ".SV" + str(zSV) + ".";
  usbPortWrite(msgTx); drawFlag = 1;
}

// ----------------------------------------------------------------------

void centreJoyX() {
  // centre the joystick based on the current limits, LL, UL
  servoVal[servoTgt] = servoVLL[servoTgt] + ((servoVUL[servoTgt] - servoVLL[servoTgt])/2);
  setSliderX(); drawFlag = 1;
}

// ----------------------------------------------------------------------

void centreServo() {
  // centre the servo based on the current limits, LL, UL
  centreJoyX(); servoLast = servoVal[servoTgt];
  msgTx = "SV" + str(servoVal[servoTgt]) + ".";
  usbPortWrite(msgTx);
}

// ----------------------------------------------------------------------

void ClearChEn() {
  // clear the channel enable flags
  for (int zC = 0; zC < 16; zC++) {Ch_En[zC] = false;}
  Ch_Cnt = 0;
}

// ----------------------------------------------------------------------

void ClearMemLamps() {
  // clear the memory state flags
  for (int zC = 0; zC < 16; zC++) {memLamp[zC] = 1;}
}

// ----------------------------------------------------------------------

void closeCOMPort() {
  // Close the COM port and empty the serial buffer
  if (comFlag > 0) {
    usbPort.clear(); usbPort.stop();
    comFlag = 0; msgCOM = "----"; drawFlag = 1;
    msgTx = "COM Port Closed"; println(msgTx);
    servoOE = 1;  // turn OFF OE enabled lamp
    ClearChEn();  // clear all of the channel ON flags
    Dither = false;
  }
}

// ----------------------------------------------------------------------

void copyToClipboard() {
  // called when user clicks on 'Copy' button
  // the code either copies a declaration of arrays or a list to the clipboard
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't copy if not in default mode

  int zS; String zH = ""; String zN = "";
  if (mB == LEFT) {
    // left mouse click so copy a declaration of arrays
    CopyData = "// Define PWM freq and servo calibration arrays" + CR;
    CopyData = CopyData + "#define Freq " + str(Freq) + "  // MG996R servos run at ~50 Hz updates" + CR;
    CopyData = CopyData + "// Servo:   { 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F }" + CR;
    CopyData = CopyData + "int SLL[] = {";
    for (zS = 0; zS < 16; zS++) {
      CopyData = CopyData + str(memVLL[zS]);
      if (zS < 15) {CopyData = CopyData + ",";}
    }
    CopyData = CopyData + "};  // servo lower limit values" + CR;
    CopyData = CopyData + "int SRV[] = {";
    for (zS = 0; zS < 16; zS++) {
      CopyData = CopyData + str(memVal[zS]);
      if (zS < 15) {CopyData = CopyData + ",";}
    }
    CopyData = CopyData + "};  // reference values used to set a servo" + CR;
    CopyData = CopyData + "int SUL[] = {";
    for (zS = 0; zS < 16; zS++) {
      CopyData = CopyData + str(memVUL[zS]);
      if (zS < 15) {CopyData = CopyData + ",";}
    }
    CopyData = CopyData + "};  // servo upper limit values" + CR;
    msgTx = "Arrays Copied"; drawTxMsg();
  } else {
    // right mouse click so copy a definition list
    CopyData = CR + "// Define servo Cal. constants using Hex references" + CR;
    CopyData = CopyData + "#define Freq " + str(Freq) + "  // MG996R servos run at ~50 Hz updates" + CR;
    // create lower limit list
    for (zS = 0; zS < 16; zS++) {
      zH = str(zS);
      if (zS == 10) {zH = "A";}
      if (zS == 11) {zH = "B";}
      if (zS == 12) {zH = "C";}
      if (zS == 13) {zH = "D";}
      if (zS == 14) {zH = "E";}
      if (zS == 15) {zH = "F";}
      zN = str(zS); if (zS < 10) {zN = "0" + zN;} 
      CopyData = CopyData + "#define SLL" + zH + " " + str(memVLL[zS]);
      CopyData = CopyData + "\t// servo " + zN + " lower limit value" + CR;
    }
    // create centre value list
    for (zS = 0; zS < 16; zS++) {
      zH = str(zS);
      if (zS == 10) {zH = "A";}
      if (zS == 11) {zH = "B";}
      if (zS == 12) {zH = "C";}
      if (zS == 13) {zH = "D";}
      if (zS == 14) {zH = "E";}
      if (zS == 15) {zH = "F";}
      zN = str(zS); if (zS < 10) {zN = "0" + zN;} 
      CopyData = CopyData + "#define SRV" + zH + " " + str(memVal[zS]);
      CopyData = CopyData + "\t// servo " + zN + " reset value" + CR;
    }
    // create upper limit list
    for (zS = 0; zS < 16; zS++) {
      zH = str(zS);
      if (zS == 10) {zH = "A";}
      if (zS == 11) {zH = "B";}
      if (zS == 12) {zH = "C";}
      if (zS == 13) {zH = "D";}
      if (zS == 14) {zH = "E";}
      if (zS == 15) {zH = "F";}
      zN = str(zS); if (zS < 10) {zN = "0" + zN;} 
      CopyData = CopyData + "#define SUL" + zH + " " + str(memVUL[zS]);
      CopyData = CopyData + "\t// servo " + zN + " upper limit value" + CR;
    }
    msgTx = "List Copied"; drawTxMsg();
  }
  // now copy it
  StringSelection data = new StringSelection(CopyData);
  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  clipboard.setContents(data, data);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void countChannels() {
  // called to get a handle on the number of active channels
  Ch_Cnt = 0;
  for (int zC = 0; zC < 16; zC++) {
    if (Ch_En[zC] == true) {Ch_Cnt++;}
  }
}

// ----------------------------------------------------------------------

void decActiveSliders() {
  // normally called from a keyboard left arrow key event.
  // It will decrement any active slider value
  int zservoTgt = servoTgt;  // note current target value
  for (servoTgt = 0; servoTgt < 16; servoTgt++) {
    if (Ch_En[servoTgt]) {decXSlider();}
  }
  servoTgt = zservoTgt;  // restore target value
}

// ----------------------------------------------------------------------

void decDither() {
  // reduce the dither value and send it to the Arduino
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't apply dither if not in default mode
  
  if (DitherVal > 1) {
    DitherVal--; drawFlag = 1;
    //msgTx = "SF" + str(Freq) + ".";
    //usbPortWrite(msgTx);
  }
  if (Dither) {DitherCnt = 50;  DitherPhase = 0;}  // display the change
}

// ----------------------------------------------------------------------

void decFreq() {
  // reduce the PWM frequency value and send it to the Arduino
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't dec freq. if not in default mode

  if (Freq > 40) {
    Freq--; drawFlag = 1;
    msgTx = "SF" + str(Freq) + ".";
    usbPortWrite(msgTx);
  }
}

// ----------------------------------------------------------------------

void decXSlider() {
  // decrement target slider value
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (servoVal[servoTgt] > servoVLL[servoTgt]) {
    servoLast = servoVal[servoTgt];
    if (keySHIFT) {servoVal[servoTgt] -= 5;} else {servoVal[servoTgt]--;}
    if (servoVal[servoTgt] < servoVLL[servoTgt]) {servoVal[servoTgt] = servoVLL[servoTgt];}
    setXSlider();
    if (AppMode == 0) {
      if (Ch_En[servoTgt]) {
        msgTx = "ST" + str(servoTgt) + "." +"SV" + str(servoVal[servoTgt]) + ".";
      } else {
        msgTx = "ST" + str(servoTgt) + "." +"SV0.";
      }
      usbPortWrite(msgTx);
    }
    if (AppMode == 1) {
      msgTx = "ST" + str(servoTgt) + "." +"SA" + str(servoVal[servoTgt]) + ".";
      if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
    }
    if (AppMode == 2) {
      msgTx = "ST" + str(servoTgt) + "." +"SV" + str(servoVal[servoTgt]) + ".";
      if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
    }
    if (AppMode == 3) {
      msgTx = "LT" + str(servoTgt) + "." +"LV" + str(servoVal[servoTgt]) + ".";
      if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
    }
    valueChanged();
    drawFlag = 1;
  }
}

// ----------------------------------------------------------------------

void exit() {
  // program is closing so reset Arduino...
  usbPortWrite("XX.");
  delay(20); // allow 20ms for data to be sent
  super.exit();
}

// ----------------------------------------------------------------------

void getCOMPort() {
  // initialise serial comms if a port exists
  if (comFlag > 0) {
    // stops an existing connection if already made
    closeCOMPort(); delay(100);
  }
  comListLength = Serial.list().length;
  if (comListLength > 0) {
    comPnt++; if (comPnt >= comListLength) {comPnt = 0;}
    // test available serial port
    comName = Serial.list()[comPnt];
    println(comName);
    try {
      usbPort = new Serial(this, comName, 115200);
      //usbPort.bufferUntil(LF);
      comFlag = 1; msgCOM = comName;
      msgTx = "USB Connected"; println(msgTx);
    } catch(Exception e) {
      comFlag = 0; msgCOM = "-Error-";
    }
  } else {
    // no serial port available
    comFlag = 0; msgCOM = "- NA -";
  } drawFlag = 1;
}

// ----------------------------------------------------------------------

void incActiveSliders() {
  // normally called from a keyboard right arrow key event.
  // It will increment any active slider value
  int zservoTgt = servoTgt;  // note current target value
  for (servoTgt = 0; servoTgt < 16; servoTgt++) {
    if (Ch_En[servoTgt]) {incXSlider();}
  }
  servoTgt = zservoTgt;  // restore target value
}

// ----------------------------------------------------------------------

void incDither() {
  // increase the dither value and send it to the Arduino
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't inc if in angle mode

  if (DitherVal < 20) {
    DitherVal++; drawFlag = 1;
    //msgTx = "SF" + str(Freq) + ".";
    //usbPortWrite(msgTx);
  }
  if (Dither) {DitherCnt = 50;  DitherPhase = 0;}  // display the change
}

// ----------------------------------------------------------------------

void incFreq() {
  // increase the PWM frequency value and send it to the Arduino
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't inc if in angle mode

  if (Freq < 100) {
    Freq++; drawFlag = 1;
    msgTx = "SF" + str(Freq) + ".";
    usbPortWrite(msgTx);
  }
}

// ----------------------------------------------------------------------

void incXSlider() {
  // increment slider value
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (servoVal[servoTgt] < servoVUL[servoTgt]) {
    servoLast = servoVal[servoTgt];
    if (keySHIFT) {servoVal[servoTgt] += 5;} else {servoVal[servoTgt]++;}
    if (servoVal[servoTgt] > servoVUL[servoTgt]) {servoVal[servoTgt] = servoVUL[servoTgt];}
    setXSlider();
    if (AppMode == 0) {
      if (Ch_En[servoTgt]) {
        msgTx = "ST" + str(servoTgt) + "." +"SV" + str(servoVal[servoTgt]) + ".";
      } else {
        msgTx = "ST" + str(servoTgt) + "." +"SV0.";
      }
      usbPortWrite(msgTx);
    }
    if (AppMode == 1) {
      msgTx = "ST" + str(servoTgt) + "." +"SA" + str(servoVal[servoTgt]) + ".";
      if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
    }
    if (AppMode == 2) {
      msgTx = "ST" + str(servoTgt) + "." +"SV" + str(servoVal[servoTgt]) + ".";
      if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
    }
    if (AppMode == 3) {
      msgTx = "LT" + str(servoTgt) + "." +"LV" + str(servoVal[servoTgt]) + ".";
      if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
    }
    valueChanged();
    drawFlag = 1;
  }
}

// ----------------------------------------------------------------------

void LoadDefArrays() {
  // load the calibration values into the arrays
  // if you fit a new servo you need to relax values to determine limits
  // relax value limits are LL=120, UL=590
  int zVal = 350; int zVLL = 100; int zVUL = 600;
  if (AppMode == 1) {zVal = 135; zVLL = 0; zVUL = 270;}
  if (AppMode == 2) {zVal = 1500; zVLL = 300; zVUL = 2700;}
  if (AppMode == 3) {zVal = 0; zVLL = -1000; zVUL = 1000;}
  for (int zS = 0; zS < 16; zS++) {
    // Set default values
    servoVal[zS]  = zVal; servoVLL[zS]  = zVLL; servoVUL[zS]  = zVUL;
    memVal[zS]  = zVal; memVLL[zS]  = zVLL; memVUL[zS]  = zVUL;
    memLamp[zS] = 1;
  }
}

// ----------------------------------------------------------------------

void MemHeld() {
  // user clicked on mem lamp for more than 1 second so store values
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}       // don't draw if in angle mode

  cursor(WAIT);
  memVLL[servoTgt] = servoVLL[servoTgt];
  memVal[servoTgt] = servoVal[servoTgt];
  memVUL[servoTgt] = servoVUL[servoTgt];
  memLamp[servoTgt] = 3; // set lamp blue
  mDwn = 0;              // reset the mouse down flag
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void MemPressed() {
  // user clicked on a memory lamp
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't action if in angle mode

  memState = memLamp[servoTgt];   // record the current lamp setting
  memLamp[servoTgt] = 0;          // switch OFF lamp whilst pressed
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void MemRecall() {
  // recall the channel settings from memory
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't action if in angle mode

  servoVLL[servoTgt] = memVLL[servoTgt];
  servoVal[servoTgt] = memVal[servoTgt];
  servoVUL[servoTgt] = memVUL[servoTgt];
  setSliderX();            // reposition X slider
  // reset the lamp field
  switch(memState) {
    case 1: memLamp[servoTgt] = 1; break;   // reset lamp
    case 2: memLamp[servoTgt] = 1; break;   // reset lamp
    case 3: memLamp[servoTgt] = 3; break;   // reset lamp
    case 4: memLamp[servoTgt] = 3; break;   // reset lamp
  }
  // send the recalled settings to the Arduino
  msgTx = "ST" + str(servoTgt) + ".";
  if (Ch_En[servoTgt]) {
    msgTx = msgTx +"SV" + str(servoVal[servoTgt]) + ".";
  } else {
    msgTx = msgTx +"SV0.";
    }
  usbPortWrite(msgTx); 
  msgTx = "ST" + str(servoTgt) + ".";
  msgTx = msgTx + "SL" + str(servoVLL[servoTgt]) + ".";
  usbPortWrite(msgTx); 
  msgTx = "ST" + str(servoTgt) + ".";
  msgTx = msgTx + "SU" + str(servoVUL[servoTgt]) + ".";
  usbPortWrite(msgTx); 
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void readXSlider(int zS) {
  // determine X-slider position and send value
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  servoLast = servoVal[zS];  // remember the current value
  JoyXval[zS] = mX - mXD;
  JoyXval[zS] = max(JoyXval[zS], JoyX0);
  JoyXval[zS] = min(JoyXval[zS], JoyX1);
  // normal single channel mode
  servoVal[zS] = servoVLL[zS] + (((JoyXval[zS] - JoyX0) * (servoVUL[servoTgt] - servoVLL[servoTgt])) / (JoyX1 - JoyX0));
  servoVal[zS] = max(servoVal[zS], servoVLL[zS]);
  servoVal[zS] = min(servoVal[zS], servoVUL[zS]);
  if (servoLast != servoVal[zS]) {
    servoLast = servoVal[zS];
    valueChanged();
    if (AppMode == 0) {
      // in normal 16-ch PWM mode
      if (Ch_En[zS]) {
        msgTx = "ST" + str(zS) + "." +"SV" + str(servoVal[zS]) + ".";
      } else {
        msgTx = "ST" + str(zS) + "." +"SV0.";
      }
      usbPortWrite(msgTx);
    }
    if (AppMode == 1) {
      // in 16-Ch angle mode
      msgTx = "ST" + str(zS) + "." +"SA" + str(servoVal[zS]) + ".";
      if (Ch_En[zS]) {usbPortWrite(msgTx);}
    }
    if (AppMode == 2) {
      // in PWM mode
      msgTx = "ST" + str(zS) + "." +"SV" + str(servoVal[zS]) + ".";
      if (Ch_En[zS]) {usbPortWrite(msgTx);}
    }
    if (AppMode == 3) {
      // in LX-16A mode
      msgTx = "LT" + str(zS) + "." +"LV" + str(servoVal[zS]) + ".";
      if (Ch_En[zS]) {usbPortWrite(msgTx);}
    }
    drawFlag = 1;
  }
}

// ----------------------------------------------------------------------

void RstFreq() {
  // reset the PWM frequency to 50Hz or 60Hz depending on mouse click
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't action if in angle mode

if (mB == LEFT) {Freq = 50;}
  else {Freq = 60;} drawFlag = 1;
  msgTx = "SF" + str(Freq) + ".";
  usbPortWrite(msgTx);
}

// ----------------------------------------------------------------------

void sendButton(String zS) {
  // called when a button is clicked on the scrn
  if (comFlag < 1) {return;}  // don't do anything if not connected
  msgTx = zS; usbPortWrite(msgTx);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void sendExpRq() {
  // send an EXPORT request to the Arduino
  msgTx = "SE."; usbPortWrite(msgTx);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void sendRESET() {
  // reset defaults and send RESET msg to arduino
  servoOE = 1;      // clear OE flag as Arduino will do the same
  setMode();
  ClearChEn();
  ClearMemLamps();
  // the Arduino should RESET and export the data values
  msgTx = "!"; usbPortWrite(msgTx);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void sendServoDetach() {
  // Sends a detach command to the target servo
  msgTx = "SD" + str(servoTgt) + ".";
  usbPortWrite(msgTx);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void sendServoVal() {
  // send the current channels servo value
  servoLast = servoVal[servoTgt];
  msgTx = "ST" + str(servoTgt) + "." +"SV" + str(servoVal[servoTgt]) + ".";
  if (Ch_En[servoTgt]) {usbPortWrite(msgTx);}
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void SetChEn() {
  // Sets all channel enables the same and sends data to the Arduino
  if (comFlag < 1) {return;}  // don't do anything if not connected
  
  int zS = servoTgt; boolean zEn = Ch_En[servoTgt];
    // Setting all channel enables to true
  for (servoTgt = 0; servoTgt < 16; servoTgt++) {
    Ch_En[servoTgt] = Ch_En[zS];
    if (zEn) {sendServoVal();} else {sendServoDetach();}
  }
  servoTgt = zS; drawFlag = 1;
  
  countChannels();
  if ((Ch_Cnt < 1) && (Dither)) {turnDitherOFF();}
}

// ----------------------------------------------------------------------

void setLL() {
  // set lower limit from slider position
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  LastVal = servoVLL[servoTgt];
  if (mB == LEFT) {
    servoVLL[servoTgt] = servoVal[servoTgt];
    if (servoVLL[servoTgt] >= servoVUL[servoTgt]) {servoVLL[servoTgt]--;}
  } else {
    servoVLL[servoTgt] = servoLL;
  }
  setXSlider(); drawFlag = 1;
  msgTx = "SL" + str(servoVLL[servoTgt]) + ".";
  usbPortWrite(msgTx);
  if (LastVal != servoVLL[servoTgt]) {valueChanged();}
}

// ----------------------------------------------------------------------

void setLLValDwn() {
  // reduce the slider lower limit
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  if (servoVLL[servoTgt] > servoLL) {
    servoVLL[servoTgt]--;
    if (keySHIFT) {servoVLL[servoTgt] -= 5;} else {servoVLL[servoTgt]--;}
    if (servoVLL[servoTgt] < servoLL) {servoVLL[servoTgt] = servoLL;}
    setXSlider(); drawFlag = 1;
    msgTx = "SL" + str(servoVLL[servoTgt]) + ".";
    usbPortWrite(msgTx);
    valueChanged();
  }
}

// ----------------------------------------------------------------------

void setLLValUp() {
  // increase the slider upper limit
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}

  if (servoVLL[servoTgt] < servoUL) {
    if (keySHIFT) {servoVLL[servoTgt] += 5;} else {servoVLL[servoTgt]++;}
    if (servoVLL[servoTgt] > servoUL) {servoVLL[servoTgt] = servoUL;}
    if (servoVLL[servoTgt] >= servoVUL[servoTgt]) {servoVUL[servoTgt]++;}
    if (servoVal[servoTgt] < servoVLL[servoTgt]) {servoVal[servoTgt] = servoVLL[servoTgt];}
    setXSlider(); drawFlag = 1;
    msgTx = "SL" + str(servoVLL[servoTgt]) + ".";
    usbPortWrite(msgTx);
    valueChanged();
  }
}

// ----------------------------------------------------------------------

void setMode() {
  // change the mode of the application
  switch(AppMode) {
    case 0:
      // switch form to default mode
      servoLL = 50; servoUL = 600;
      LoadDefArrays(); setXSlider(); break;
    case 1:
      // switch form into angle mode
      servoLL = 0; servoUL = 270;
      LoadDefArrays(); setXSlider(); break;
    case 2:
      // switch form into PWM mode
      servoLL = 300; servoUL = 2700;
      LoadDefArrays(); setXSlider(); break;
    case 3:
      // switch form into LX-16A Serial mode
      servoLL = -1000; servoUL = 1000;
      msTimer = 0; msRun = false;  // start with timer reset
      LoadDefArrays(); setXSlider(); break;
  }
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void setSliderX() {
  // set the X-slider position for current servoVal[servoTgt] value
  if ((comFlag < 1) && (Loaded)) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  int zX0 = 259; int zX1 = 655; int zSV = servoVal[servoTgt];
  if (Dither) {
    zSV += DitherOffset;
    zSV = max(zSV,servoVLL[servoTgt]); zSV = min(zSV,servoVUL[servoTgt]);
  }
  JoyXval[servoTgt] = zX0 + ((zSV - servoVLL[servoTgt]) * (zX1 - zX0))/(servoVUL[servoTgt] - servoVLL[servoTgt]);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void setUL() {
  // set upper limit from slider position if left mouse click
  // otherwise use absolute limit
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  LastVal = servoVUL[servoTgt];
  if (mB == LEFT) {
    servoVUL[servoTgt] = servoVal[servoTgt];
    if (servoVUL[servoTgt] <= servoVLL[servoTgt]) {servoVUL[servoTgt]++;}
  } else {
    servoVUL[servoTgt] = servoUL;
  }
  setXSlider(); drawFlag = 1;
  msgTx = "SU" + str(servoVUL[servoTgt]) + ".";
  usbPortWrite(msgTx);
  if (LastVal != servoVUL[servoTgt]) {valueChanged();}
}

// ----------------------------------------------------------------------

void setULValDwn() {
  // reduce the slider upper limit
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  if (servoVUL[servoTgt] > servoLL) {
    if (keySHIFT) {servoVUL[servoTgt] -= 5;} else {servoVUL[servoTgt]--;}
    if (servoVUL[servoTgt] < servoLL) {servoVLL[servoTgt] = servoLL;}
    if (servoVUL[servoTgt] <= servoVLL[servoTgt]) {servoVLL[servoTgt]--;}
    if (servoVal[servoTgt] > servoVUL[servoTgt]) {servoVal[servoTgt] = servoVUL[servoTgt];}
    setXSlider(); drawFlag = 1;
    msgTx = "SU" + str(servoVUL[servoTgt]) + ".";
    usbPortWrite(msgTx);
    valueChanged();
  }
}

// ----------------------------------------------------------------------

void setULValUp() {
  // increase the slider upper limit
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  if (servoVUL[servoTgt] < servoUL) {
    if (keySHIFT) {servoVUL[servoTgt] += 5;} else {servoVUL[servoTgt]++;}
    if (servoVUL[servoTgt] > servoUL) {servoVUL[servoTgt] = servoUL;}
    setXSlider(); drawFlag = 1;
    msgTx = "SU" + str(servoVUL[servoTgt]) + ".";
    usbPortWrite(msgTx);
    valueChanged();
  }
}

// ----------------------------------------------------------------------

void setXSlider() {
  // set the X-slider position for all servoVal[n] values
  if ((comFlag < 1) && (Loaded)) {return;}  // don't do anything if not connected
  if ((AppMode == 3) && (servoTgt == 0)) {return;}
  
  int zX0 = 259; int zX1 = 655;
  for (int zS = 0; zS < 16; zS++) {
    JoyXval[zS] = zX0 + ((servoVal[zS] - servoVLL[zS]) * (zX1 - zX0))/(servoVUL[zS] - servoVLL[zS]);
  }
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void toggleChEn() {
  // toggles the channel enable and sends data if channel is turned ON
  if (comFlag < 1) {return;}  // don't do anything if not connected
  
  Ch_En[servoTgt] = !Ch_En[servoTgt];
  if (Ch_En[servoTgt]) {msgTx = "ST" + str(servoTgt) + ".SV" + str(servoVal[servoTgt]) + ".";}
  else {msgTx = "SD" + str(servoTgt) + ".";}  // disable the channel
  usbPortWrite(msgTx);
  drawFlag = 1;
}

// ----------------------------------------------------------------------

void toggleDither() {
  // called to switch Dither ON/OFF
  if (AppMode > 0) {return;} // don't action if in angle mode

  drawFlag = 1;
  if (Ch_Cnt < 1)  {turnDitherOFF(); return;}
  
  if (Dither) {turnDitherOFF();}
  else {Dither = true;}  
  Help_Message();
}

// ----------------------------------------------------------------------

void toggleMode() {
  // change the mode of the application
  AppMode++; if (AppMode > 3) {AppMode = 0;}
  setMode();
}

// ----------------------------------------------------------------------

void toggleMsTimer() {
  // user has clicked on msTimer field so start/stop timer
  if (msRun) {
    // timer is running, so stop it
    msRun = false; msTimer = millis() - msTimer;
  } else {
    // timer is not running, so start it
    msRun = true; msTimer = millis();
  }
  DFU = true;  // redraw on mouse release
}

// ----------------------------------------------------------------------

void toggleOE() {
  // toggle the OE flag and update
  if (comFlag < 1) {return;}  // don't do anything if not connected
  if (AppMode > 0) {return;}  // don't action if in angle mode

  if (servoOE > 0) {
    // turning OE ON
    servoOE = 0; sendButton("SO0.");
  }
  else {
    // turning OE OFF
    servoOE = 1; sendButton("SO1.");
    ClearChEn();  // clear channels to indicate servo pulses are OFF
    turnDitherOFF();  // remove dither if it was ON
  } drawFlag = 1;
}

// ----------------------------------------------------------------------

void turnDitherOFF() {
  // called when Dither is active to turn it OFF
  Dither = false; DitherPhase = 0; DitherOffset = 0;
  appendOffset();
}

// ----------------------------------------------------------------------

void usbPortOp() {
  // called from a timer to empty the usb buffer
  usbLen = usbBuff.length();
  if (usbLen < 1) {return;}  // nothing in buffer
  
  // we are going to send something from usbBuff
  if (usbLen <= WiFiPW) {
    // it should only take 2.8ms to send 32 bytes
    usbPort.write(usbBuff);  // send small packet
    usbBuff = ""; usbLen = 0; // send small packet and clear buffer
  } else {
    usbPort.write(usbBuff.substring(0,WiFiPW)); // send WiFiPW length packet
    usbBuff = usbBuff.substring(WiFiPW,usbBuff.length());  // shift buffer by 16 char
    usbLen = usbBuff.length();
  }
}

// ----------------------------------------------------------------------

void usbPortWrite(String zmsg) {
  // called to send text over the serial port
  // nothing is sent if comFlag or usbEn are false
  if (comFlag > 0) {
    usbBuff = usbBuff + zmsg;  // add new message to buffer
    //println(zmsg);
  } else {usbBuff = "";}  // flush the buffer if not sending
}

// ----------------------------------------------------------------------

void valueChanged() {
  // called whenever a value changes to set lamps
  // if a lamp was green it will change to yellow
  // if a lamp was blue it will change to purple
  if (comFlag < 1) {return;}  // don't do anything if not connected
  switch(memLamp[servoTgt]) {
    case 1: memLamp[servoTgt] = 2; break;
    case 3: memLamp[servoTgt] = 4; break;
  }
}
