/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* LFOno - an Arduino-based multi-mode LFO
* Beavis Audio Research, March 2008
* beavisaudio.com
*
* Copyright (c) 2008 Beavis Audio Research
* Use limited to non-commercial only
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
* Revision History
* 3/5/08 djh - initial version
*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

// LCD control constants
#define SERLCD_CMD 0xFE
#define SERLCD_XCMD 0x7C // Dec 124
#define SERLCD_CTRLK 0x0B
#define SERLCD_CTRLM 0x0D
#define SERLCD_CTRLJ 0x0A
#define SERLCD_CLEAR 001
#define SERLCD_MOVE1_R 014
#define SERLCD_MOVE1_L 010
#define SERLCD_SCROLL_R 01C
#define SERLCD_SCROLL_L 018
#define SERLCD_DISP_ON 00C
#define SERLCD_DISP_OFF 008
#define SERLCD_UNDERLINE_CURSOR_ON 00E
#define SERLCD_UNDERLINE_CURSOR_OFF 00C
#define SERLCD_BLINKING_CURSOR_ON 00D
#define SERLCD_BLINKING_CURSOR_OFF 00C
#define SERLCD_SET_CURSOR_POS 080
#define SERLCD_CURSOR_OFF 00C

int pitchval = 1;

const int PIN_RATE = 0;
const int PIN_MODE = 7;
const int PIN_MODEPOT = 1;
const int PIN_MODETWEAK = 2;
const int PIN_PWMOUT = 9;
const int MODE_CHOPPER = 0;
const int MODE_SOFTCHOP = 100;
const int MODE_WAVE = 200;
const int MODE_STAIR = 300;
const int MODE_RANDOM = 400;
const int MODE_FREQ = 500;
const int MODE_TUNABLE = 600;
const int MODE_USERSEQ = 700;
const int RAND_LOW_THRESHOLD = 30;
const int RAND_HIGH_THRESHOLD = 255;

int LFOMode = 0;
int tweak = 0;
int tempo = 0;
int oldTempo;
int oldLFOMode;



void setup ()
{
pinMode (PIN_PWMOUT, OUTPUT);
pinMode (PIN_MODE, INPUT);
pinMode (PIN_MODEPOT, INPUT);
pinMode (PIN_MODETWEAK, INPUT);

randomSeed (analogRead (0));

LCD_Init();

StartupMessage();
}

void StartupMessage()
{
LCD_Clear();
Serial.print ("beavis DIGITAL");
delay(300);
Serial.print (254, BYTE);
Serial.print (192, BYTE);
Serial.print ("Ass Tornado");
delay (2000);
LCD_Clear();
Serial.print ("DSWF 1.0");
}



void CheckControls()
{
// cache previous state
oldTempo = tempo;
oldLFOMode = LFOMode;
tempo = analogRead (PIN_RATE);

tweak = analogRead (PIN_MODETWEAK);

int v = analogRead (PIN_MODEPOT);
if (v >=0 && v <=125)
{
LFOMode = MODE_CHOPPER;
}
else if (v >125 && v<=250)
{
LFOMode = MODE_SOFTCHOP;
}
else if (v > 250 && v<=375)
{
LFOMode = MODE_WAVE;
}
else if (v > 375 && v<=500)
{
LFOMode = MODE_STAIR;
}
else if (v > 500 && v<625)
{
LFOMode = MODE_RANDOM;
}
else if (v >625 && v<=750)
{
LFOMode = MODE_FREQ;
}
else if (v >750 && v<=825)
{
LFOMode = MODE_TUNABLE;
}
else if (v >825)
{
LFOMode = MODE_USERSEQ;
}

UpdateDisplay();
// if user vars have changed, update the diaplay
if (oldTempo != tempo || oldLFOMode != LFOMode)
{

UpdateDisplay();
}
}

void loop()
{
int counter = 0;
bool controlsChanged = false;
tempo = analogRead (PIN_RATE) * 1.9;


if (LFOMode == MODE_CHOPPER)
{
// chop wave mode
digitalWrite (PIN_PWMOUT, HIGH);
delay (tempo);
digitalWrite (PIN_PWMOUT, LOW);
delay (tempo);
CheckControls();
}
else if (LFOMode == MODE_SOFTCHOP)
{
analogWrite (PIN_PWMOUT, 25);
delay (tempo/9);
analogWrite (PIN_PWMOUT, 50);
delay (tempo/9);
analogWrite (PIN_PWMOUT, 75);
delay (tempo/9);
analogWrite (PIN_PWMOUT, 100);
delay (tempo/9);
analogWrite (PIN_PWMOUT, 150);
delay (tempo/9);
analogWrite (PIN_PWMOUT, 180);
delay (tempo/9);
analogWrite (PIN_PWMOUT, 255);
delay (tempo/2);

CheckControls();
}
else if (LFOMode == MODE_WAVE)
{
// fake sine wave mode
for(counter = 0 ; counter <= 255; counter+=20)
{
analogWrite(PIN_PWMOUT, counter);
if (tweak >500)
{
delay (tempo/20);
}
else
{
delay (tempo/40);
analogWrite(PIN_PWMOUT, 125);
delay (tempo/20);
}
CheckControls();
}
for (counter = 0; counter >= 100; counter-20)
{
analogWrite (PIN_PWMOUT, counter);
if (tweak >500)
{
delay (tempo/20);
}
else
{
delay (tempo/40);
analogWrite(PIN_PWMOUT, 125);
delay (tempo/20);
}
CheckControls();
}
//CheckControls();
}
else if (LFOMode == MODE_RANDOM)
{
// random mode, get a random number between thresholds
int randNumber = random(RAND_LOW_THRESHOLD, RAND_HIGH_THRESHOLD);
analogWrite (PIN_PWMOUT, randNumber);
delay (tempo/2);
analogWrite (PIN_PWMOUT, 255 - randNumber);
delay (tempo/2);
CheckControls();
}

else if (LFOMode == MODE_FREQ)
{

int randNumber = random(tweak,tweak*4);
freqout (randNumber, tempo);
CheckControls();

}
else if (LFOMode == MODE_STAIR)
{
int counter = 0;

for (counter = 0; counter <= 255; counter += 25)
{
analogWrite (PIN_PWMOUT, counter);
delay (tempo/10);
CheckControls();
}
for (counter = 255; counter >=0; counter -= 25)
{
analogWrite (PIN_PWMOUT, counter);
delay (tempo/10);
CheckControls();
}
}
else if (LFOMode == MODE_TUNABLE)
{
int fixed = analogRead (PIN_MODETWEAK);
freqout (fixed, tempo/2);
freqout (1024-fixed, tempo/2);
CheckControls();
}
else if (LFOMode == MODE_USERSEQ)
{
CheckControls();
}
}

// Take current state and update the LCD display
void UpdateDisplay()
{
LCD_Clear();
Serial.print ("r:");
Serial.print (tempo);
Serial.print (" twk:");
Serial.print (tweak);
LCD_Newline();

// Update the display and the status LEDs with the current mode
if (LFOMode == MODE_CHOPPER)
{
Serial.print ("mode:CHOPPER");
}
else if (LFOMode == MODE_SOFTCHOP)
{
Serial.print ("mode:SOFT CHOP");
}
else if (LFOMode == MODE_WAVE)
{
Serial.print ("mode:WAVE");
}
else if (LFOMode == MODE_RANDOM)
{
Serial.print ("mode:RANDOM");
}
else if (LFOMode == MODE_STAIR)
{
Serial.print ("mode:STAIR");
}
else if (LFOMode == MODE_FREQ)
{
Serial.print ("mode:FREQ");
}
else if (LFOMode == MODE_TUNABLE)
{
Serial.print ("mode:TUNABLE");
}
else if (LFOMode == MODE_USERSEQ)
{
Serial.print ("mode:USER SEQ");
}
}

//freqout code by Paul Badger
// freq - frequency value
// t - time duration of tone
void freqout(int freq, int t)
{
int hperiod; //calculate 1/2 period in us
long cycles, i;

// subtract 7 us to make up for digitalWrite overhead - determined empirically
hperiod = (500000 / ((freq - 7) * pitchval));

// calculate cycles
cycles = ((long)freq * (long)t) / 1000; // calculate cycles

for (i=0; i<= cycles; i++)
{ // play note for t ms
digitalWrite(PIN_PWMOUT, HIGH);
delayMicroseconds(hperiod);
digitalWrite(PIN_PWMOUT, LOW);
delayMicroseconds(hperiod - 1); // - 1 to make up for fractional microsecond in digitaWrite overhead
}
}

//*******************************************
// LCD STUFF
// beavis audio/march 2008


void LCD_Clear()
{
Serial.print(254, BYTE);
Serial.print(1, BYTE);
}

/* LCD_SetBacklight - set the brightness of the backlight
int Bright - 128=lowest, 157=brightest */
void LCD_SetBacklight (int bright)
{
// LCD expects values between 128 and 157
int value = bright + 127;

Serial.print (SERLCD_XCMD, BYTE);
Serial.print (value, BYTE);
}

void LCD_Init()
{
Serial.begin (9600);
Serial.print (SERLCD_XCMD, BYTE);
Serial.print (SERLCD_CTRLM, BYTE);
delay (300);

LCD_Clear();

// Set up the boot up message, this only needs to be
// flashed once

/* Serial.print ("beavis Digital");
Serial.print (254, BYTE);
Serial.print (192, BYTE);
Serial.print ("DSWF 1.0");
Serial.print (SERLCD_XCMD, BYTE);
Serial.print (SERLCD_CTRLJ, BYTE);
*/

LCD_SetBacklight (30);
}

void LCD_Newline()
{
Serial.print (254, BYTE);
Serial.print (192, BYTE);
}