/*
 * Mega-test for ME30 Mini-board
 * Written by Eric B. Wertz
 * Last updated Nov 16, 2009
 *
 * The ME30 Mini-board layout
 *   AnalogIn Pin 0 - potentiometer
 *   Digital  Pin 6 - RGB LED - blue,  active high
 *   Digital  Pin 5 - RGB LED - green, active high
 *   Digital  Pin 3 - RGB LED - red,   active high
 *   Digital  Pin 4 - speaker
 *   Digital  Pin 7 - momentary (N.O.) switch, active low
 *
 * This program demonstrates the following:
 *   - serial output
 *   - fade RGB LED up/down red, then green, then blue
 *   - sampling for button presses
 *   - read/determine full range of potentiometer (pot) values
 *   - record & play some tones on the speaker
 */

const int ANALOG_IN_MIN=0,  ANALOG_IN_MAX=1023;  // 10-bit ADCs for input
const int ANALOG_OUT_MIN=0, ANALOG_OUT_MAX=255;  //  8-bit resolution PWM for output

const int  TWO_SECONDS   =    2000 ;   // # of milliseconds in 2 seconds
const long USECS_PER_SEC = 1000000L;   // # of microseconds per second

// ANALOG pin assignments
const int pinPot     = 0;

// DIGITAL pin assignments
const int pinRGB_R   = 3;
const int pinRGB_G   = 5;
const int pinRGB_B   = 6;
const int pinSpeaker = 4;
const int pinButton  = 7;

/*
 * Number of milliseconds to wait for a switch to stop bouncing before checking again.
 * The datasheet for this particular series of button says 5ms max.
 */
const int BUTTON_DEBOUNCE_DELAY = 7;

/*
 * Number of milliseconds to light at one intensity level before changing.
 * Must be less than or equal to 655 to not overflow an "int" when multiplied
 * by 100 (percent).
 */
const unsigned int LED_STEP_PERIOD = 100;

/*
 * 10 milliseconds to get a good refresh rate (100Hz) for plusing the LEDs.
 */
const unsigned int SWPWM_PERIOD = 10;

const unsigned int POT_SAMPLING_DELAY=250;  // only check pot value every 0.25sec

const unsigned int FREQ_MIN=600, FREQ_MAX=2400;     // use two octaves for output

const unsigned int TONES_MAX=30;     // maximum number of tones to store in array
const unsigned int PLAYBACK_PERIOD=500;                // msecs to playback tones

/*
  * one-time initializations at start of program
  */
void setup() {
  Serial.begin(9600);             // init serial connection to 9600bps

  pinMode(pinButton, INPUT);
  digitalWrite(pinButton, HIGH);  // enable internal pull-up

  pinMode(pinSpeaker,OUTPUT);
  pinMode(pinRGB_R,  OUTPUT);
  pinMode(pinRGB_B,  OUTPUT);
  pinMode(pinRGB_G,  OUTPUT);
}

/* Detect (software debounced) button press */
boolean isButtonDown(int pin)
{
  if (digitalRead(pin) == HIGH)
    return false;
  delay(BUTTON_DEBOUNCE_DELAY);
  return (digitalRead(pin) == LOW);
}

/* Detect (software debounced) button release */
boolean isButtonUp(int pin)
{
  if (digitalRead(pin) == LOW)
    return false;
  delay(BUTTON_DEBOUNCE_DELAY);
  return (digitalRead(pin) == HIGH);
}

void waitForPressAndRelease(int pin)
{
  while (isButtonUp(pin))
    ;
  while (isButtonDown(pin))
    ;
}

/*
 * Use the hardware's PWM to shine the LED using a duty cycle that fades up-and-down.
 * Return indication of whether or not we were interrupted by a button press.
 */
boolean fadeLEDUsingHardware(byte ledpin, byte buttonpin, int duration)
{
  boolean buttonPressed=false;

  for (int pct=10; !buttonPressed && (pct <= 100); pct += 10) {
    analogWrite(ledpin, (pct*ANALOG_OUT_MAX)/100);
    delay(duration);
    buttonPressed = isButtonDown(buttonpin);
  }
  for (int pct=100; !buttonPressed && (pct >= 0); pct -= 10) {
    analogWrite(ledpin, (pct*ANALOG_OUT_MAX)/100);
    delay(duration);
    buttonPressed = isButtonDown(buttonpin);
   }

  return buttonPressed;
}

/*
 * PWM at "percentOn" duty cycle in SWPWM_PERIOD millisecond chunks for "duration".
 */
boolean fadeLEDUsingSoftware(byte ledpin, byte buttonpin, int percentOn, int duration)
{
  boolean buttonPressed = false;
  int     partOfPeriodOn = (SWPWM_PERIOD*percentOn)/100;

  while (!buttonPressed && (duration >= SWPWM_PERIOD)) {
    digitalWrite(ledpin, HIGH);
    delay(partOfPeriodOn);
    digitalWrite(ledpin, LOW);
    delay(SWPWM_PERIOD-partOfPeriodOn);
    duration -= SWPWM_PERIOD;
    buttonPressed = isButtonDown(buttonpin);
  }

  /* do remaining fraction of SWPWM_PERIOD, if any */
  if (!buttonPressed && (duration > 0)) {
    partOfPeriodOn = (duration*percentOn)/100;
    digitalWrite(ledpin, HIGH);
    delay(partOfPeriodOn);
    digitalWrite(ledpin, LOW);
  }
  return buttonPressed;
}

/*
 * Cycle through red then green then blue until the button is pressed.
 * Check to see if the button is depressed at every intensity step to be more responsive.
 */
void doLEDFades()
{
  boolean buttonPressed=false;

  Serial.println("Time for some cosmic LED action.  Press button when you've had enough.");
 
  while (!buttonPressed) {
    buttonPressed = fadeLEDUsingHardware(pinRGB_R, pinButton, LED_STEP_PERIOD);
    if (!buttonPressed)
      buttonPressed = fadeLEDUsingHardware(pinRGB_G, pinButton, LED_STEP_PERIOD);
    if (!buttonPressed)
      buttonPressed = fadeLEDUsingHardware(pinRGB_B, pinButton, LED_STEP_PERIOD);
  }

  while (isButtonDown(pinButton))  // wait for button to be released
    ;

  digitalWrite(pinRGB_R, LOW);
  digitalWrite(pinRGB_G, LOW);
  digitalWrite(pinRGB_B, LOW);
  Serial.println();
}

void doPotSampling(byte ledPin, unsigned int *pPotMin, unsigned int *pPotMax)
{
  unsigned int potValue, potMax=ANALOG_IN_MIN, potMin=ANALOG_IN_MAX;
  boolean      changedBounds;

  Serial.println("Sweep the pot knob over its full range.  Press button when done.");

  while (isButtonUp(pinButton)) {
    potValue = analogRead(pinPot);
    changedBounds  = false;

    if (potValue > potMax) {
      potMax = potValue;
      changedBounds = true;
    }
    if (potValue < potMin) {
      potMin = potValue;
      changedBounds = true;
    }
    analogWrite(ledPin, map(potValue, ANALOG_IN_MIN, ANALOG_IN_MAX, ANALOG_OUT_MIN, ANALOG_OUT_MAX));
    if (changedBounds) {
      Serial.print("min=");   Serial.print  (potMin);
      Serial.print(", max="); Serial.println(potMax);
    }
    delay(POT_SAMPLING_DELAY);     // don't go crazy by sampling more than every 1/4 sec
  }

  while (isButtonDown(pinButton))
    ;
  Serial.println();
  
  *pPotMin = potMin;
  *pPotMax = potMax;
  digitalWrite(ledPin, LOW);
}

void playTone(int pin, int freq, int durationMSecs)
{
  long duration   = (long)durationMSecs * 1000L,
       fullPeriod = USECS_PER_SEC/freq,
       halfPeriod = fullPeriod/2, i;

  for (i=0; i<duration; i+=fullPeriod) {
    digitalWrite(pin, HIGH);
    delayMicroseconds(halfPeriod);
    digitalWrite(pin, LOW);
    delayMicroseconds(halfPeriod);
  }
}

int recordTones(unsigned int *tones, unsigned int potMin, unsigned int potMax)
{
  unsigned int tonenum=0;
  boolean      done=false;

  Serial.println("Use the pot knob to select a tone on the speaker.  When you find a tone");
  Serial.println("that you'd like to add to your recording, press the button.  To stop");
  Serial.println("recording and playback the tune, press and hold the button for two seconds.");
  Serial.println("Press the button to get started.");

  waitForPressAndRelease(pinButton);
  Serial.println();

  do {
    unsigned int freq = map(analogRead(pinPot), potMin, potMax, FREQ_MIN, FREQ_MAX);
    playTone(pinSpeaker, freq, 100/*msec*/);

    if (isButtonDown(pinButton)) {
      unsigned long pressStart = millis();
      delay(BUTTON_DEBOUNCE_DELAY);
      while (isButtonDown(pinButton))
        ;
      if ((millis() - pressStart) > TWO_SECONDS)
           done = true;
      else tones[tonenum++] = freq;
    }
  } while (!done);

  Serial.println();
  return tonenum;
}

/*
 * Play back array of tones, half-period on and off (duty cycle 50%).
 */
void doPlayback(int pin, unsigned int numtones, unsigned int *tones, unsigned int playbackPeriod)
{
  unsigned int i, halfPeriod=(playbackPeriod/2);
  
  Serial.print("Playing song consisting of ");
  Serial.print(numtones);
  Serial.println(" tones.");
  for (i=0; i<numtones; i++) {
    playTone(pin, tones[i], halfPeriod);
    delay(halfPeriod);    
  }
  Serial.println();
}

/*
 * This is the function that gets called repeatedly from the main() function
 * provided by the Arduino framework.
 */
void loop() {
  unsigned int numtones, tones[TONES_MAX];
  unsigned int potMax, potMin;

  Serial.println("Welcome to the ME30 Mini-board test.");
  Serial.println();

  doLEDFades();

  do {
    doPotSampling(pinRGB_G, &potMin, &potMax);
  } while (potMin == potMax);  // in case the pot isn't adjusted

  numtones = recordTones(tones, potMin, potMax);
  doPlayback(pinSpeaker, numtones, tones, PLAYBACK_PERIOD);
  
  Serial.println("Press button if you would like to run this program again.");
  waitForPressAndRelease(pinButton);
  Serial.println();
}