/*
Sound & Light Machine
Firmware
for use with ATtiny2313
Make Magazine issue #10
Mitch Altman
19-Mar-07

Changes by Daniel Lanigan
11-Jan-08
*/

#include <avr/io.h>             // this contains all the IO port definitions
#include <avr/interrupt.h>      // definitions for interrupts
#include <avr/sleep.h>          // definitions for power-down modes
#include <avr/pgmspace.h>       // definitions or keeping constants in program memory

#define TIMER0_PRESCALE_1 1
#define TIMER0_PRESCALE_8 2
#define TIMER0_PRESCALE_64 3
#define TIMER0_PRESCALE_256 4
#define TIMER0_PRESCALE_1024 5
#define TIMER1_PRESCALE_1 1
#define TIMER1_PRESCALE_8 2
#define TIMER1_PRESCALE_64 3
#define TIMER1_PRESCALE_256 4
#define TIMER1_PRESCALE_1024 5

#define FREQ_BASE 400.641   // Base Left-Ear Frequency. Just change this to whatever you want.
#define BETA  14.4          // Beta frequency difference for Right ear
#define ALPHA 11.1          // Alpha frequency difference for Right ear
#define THETA 6.0           // Theta frequency difference for Right ear
#define DELTA 2.2           // Delta frequency difference for Right ear

#define LEFT_BASE 4000000/(FREQ_BASE * 256) - 1      // Value to set OCR0A (like, 38 or 77).

#define B_FREQ_BASE 4000000/(FREQ_BASE + BETA) - 1   // The Values for OCR1A for Beta.
#define A_FREQ_BASE 4000000/(FREQ_BASE + ALPHA) - 1  //       "              for Alpha.
#define T_FREQ_BASE 4000000/(FREQ_BASE + THETA) - 1  //       "              for Theta.
#define D_FREQ_BASE 4000000/(FREQ_BASE + DELTA) - 1  //       "              for Delta.

#define B_DELAY_BASE 5000/BETA                       // LED on/off time for Beta.
#define A_DELAY_BASE 5000/ALPHA                      //      "          for Alpha.
#define T_DELAY_BASE 5000/THETA                      //      "          for Theta.
#define D_DELAY_BASE 5000/DELTA                      //      "          for Delta.


/*
The hardware for this project is very simple:
     ATtiny2313 has 20 pins:
       pin 1   connects to serial port programming circuitry
       pin 10  ground
       pin 12  PB0 - Left eye LED1
       pin 13  PB1 - Right eye LED1
       pin 14  OC0A - Left ear speaker (base-frequency)
       pin 15  OC1A - Right ear speaker (Offset Frequencies for binaural beats)
       pin 17  connects to serial port programming circuitry
       pin 18  connects to serial port programming circuitry
       pin 19  connects to serial port programming circuitry
       pin 20  +3v
    All other pins are unused

    This firmware requires that the clock frequency of the ATtiny 
      is the default that it is shipped with:  8.0MHz
*/


/*
The C compiler creates code that will transfer all constants into RAM when the microcontroller
resets.  Since this firmware has a table (brainwaveTab) that is too large to transfer into RAM,
the C compiler needs to be told to keep it in program memory space.  This is accomplished by
the macro PROGMEM (this is used, below, in the definition for the brainwaveTab).  Since the
C compiler assumes that constants are in RAM, rather than in program memory, when accessing
the brainwaveTab, we need to use the pgm_read_byte() and pgm_read_dword() macros, and we need
to use the brainwveTab as an address, i.e., precede it with "&".  For example, to access 
brainwaveTab[3].bwType, which is a byte, this is how to do it:
     pgm_read_byte( &brainwaveTab[3].bwType );
And to access brainwaveTab[3].bwDuration, which is a double-word, this is how to do it:
     pgm_read_dword( &brainwaveTab[3].bwDuration );
*/


// table of values for meditation 
//   start with lots of Beta (awake / conscious)
//   add Alpha (dreamy / trancy to connect with subconscious Theta that'll be coming up)
//   reduce Beta (less conscious)
//   start adding Theta (more subconscious)
//   pulse in some Delta (creativity)
//   and then reverse the above to come up refreshed
struct brainwaveElement {
  char bwType;  // 'a' for Alpha, 'b' for Beta, 't' for Theta, or 'd' for Delta ('0' signifies last entry in table
  unsigned long int bwDuration;  // Duration of this Brainwave Type (divide by 10,000 to get seconds)
} const brainwaveTab[] PROGMEM = {
  { 'b', 600000 },
  { 'a', 100000 },
  { 'b', 200000 },
  { 'a', 150000 },
  { 'b', 150000 },
  { 'a', 200000 },
  { 'b', 100000 },
  { 'a', 300000 },
  { 'b',  50000 },
  { 'a', 600000 },
  { 't', 100000 },
  { 'a', 300000 },
  { 't', 200000 },
  { 'a', 300000 },
  { 't', 300000 },
  { 'a', 150000 },
  { 't', 600000 },
  { 'a', 150000 },
  { 'b',  10000 },
  { 'a', 150000 },
  { 't', 600000 },
  { 'd',  10000 },
  { 't', 100000 },
  { 'd',  10000 },
  { 't', 100000 },
  { 'd',  10000 },
  { 't', 300000 },
  { 'a', 150000 },
  { 'b',  10000 },
  { 'a', 150000 },
  { 't', 300000 },
  { 'a', 150000 },
  { 'b',  10000 },
  { 'a', 200000 },
  { 'b',  50000 },
  { 'a', 200000 },
  { 'b', 150000 },
  { 'a', 150000 },
  { 'b', 200000 },
  { 'a', 100000 },
  { 'b', 250000 },
  { 'a',  50000 },
  { 'b', 600000 },
  { '0',      0 }
};


// This function delays the specified number of 1/10 milliseconds
void delay_one_tenth_ms(unsigned long int ms) {
  unsigned long int timer;
  const unsigned long int DelayCount=87;  // this value was determined by trial and error

  while (ms != 0) {
    // Toggling PD0 is done here to force the compiler to do this loop, rather than optimize it away
    for (timer=0; timer <= DelayCount; timer++) {PIND |= 0b0000001;};
    ms--;
  }
}


// This function blinks the LEDs (connected to PB0, PB1 - for Left eye, Right eye, respectively)
//   at the rate determined by onTime and offTime
//   and keeps them blinking for the Duration specified (Duration given in 1/10 millisecs)
// This function also acts as a delay for the Duration specified
void blink_LEDs( unsigned long int duration, unsigned long int onTime, unsigned long int offTime) {
  for (int i=0; i<(duration/(onTime+offTime)); i++) {
    PORTB |= 0b00000011;          // turn on LEDs at PB0, PB1
    delay_one_tenth_ms(onTime);   //   for onTime
    PORTB &= 0b11111100;          // turn off LEDs at PB0, PB1
    delay_one_tenth_ms(offTime);  //   for offTime
  }
}

// This function is just to save some program memory. It takes a character (b, a, t, or d) as an input
//   and returns the correct frequecy to output to the headphones. The other input is an integer
//   that I use to get other variables, such as the light flashing rates.
//  Type can be:
//              0 - Audio output
//              1 - LED delay time

int get_freq(char state, int type) {
  if (type == 0) {
           switch (state) {        // Audio Output
              case 'b': return B_FREQ_BASE;
              case 'a': return A_FREQ_BASE;
              case 't': return T_FREQ_BASE;
              case 'd': return D_FREQ_BASE;
              default:  return LEFT_BASE * 256;
            }
  }
  else if (type == 1) {
            switch (state) {        // LED delay
              case 'b': return B_DELAY_BASE;
              case 'a': return A_DELAY_BASE;
              case 't': return T_DELAY_BASE;
              case 'd': return D_DELAY_BASE;
              default:  return 5000;
            }
  }
  return 0; // Should never get here, but the compiler whines if we don't return something :P
}



// This function transitions the autio signal from one state to the next instead of instantly switching.
//   With the settings, no transition will take longer than a second. If anyone can make this code more efficient
//   and/or less dumb looking, feel free. I don't like this function, i think it's ugly, but I haven't thought of
//   a better way to do it yet... 
// This makes the audio sound alot better. I don't bother transitioning the LEDs because it's pointless.Run this with
//   a normal meditation and as the sound changes, you will probably see the LEDs start to transition as well. But
//   obviously, they don't really do that. 
void do_audio_transition(char last, char current) {
      int last_freq = get_freq(last, 0);
      int freq = get_freq(current, 0);
      int delay = get_freq(last, 1);
      int change = 1;
      if (delay >= 400) change = delay/400;

      int i = 0;
      while (last_freq != freq) {

        if (last_freq < freq) {
          if (freq - last_freq < change) last_freq = freq - change;    // Necessary becasue sometimes the freq changes 
          last_freq += change;                                         //   at weird intervals this makes sure the loop finishes.
        }
        else if (last_freq > freq) {
            if (last_freq - freq < change) last_freq = freq + change;
            last_freq -= change; 		
        }

        OCR1A = last_freq;
        if (i == 0) {
          i = 1;
          PORTB |= 0b00000011;          // turn on LEDs at PB0, PB1
        }
        else {
          i = 0;
          PORTB &= 0b11111100;          // turn off LEDs at PB0, PB1
        }
        delay_one_tenth_ms(delay);
      } 
}

// Ok, so I can't find anything good on PWM other than the theory of how it works. So, I wrote my own. This makes
//   the LEDs turn off for increasing 87ths of a tenth of a millisecond. You send it the current blinking speed so
//   that it kttps using that, but they're not actually on as much, but they're blinking way too fast for you to
//   register, soit looks like they're dimmer. there's an extra For Loop (the one with the "again" variable) because
//   it was fading too quickly. So, it's really hacked and not the best code ever, but it works and is pretty small.
//   I don't think this will work with audio... =(

void do_LED_fade(int delay) {
  int change = 1;
  if (delay >= 400) change = delay/400;                  // if we're fading from delta it would take 19 seconds.
                                                         // so, we make it take a standard of about 3 seconds to fade.
  for (int i=87; i>0; i--) {                             // This is the main loop. Recall from above that 87 is the
                                                         //   number used in the delay_one_tenth_ms() funtion.
    for (int again = 0; again <= 2; again++) {  
      for (int timer=0; timer <= delay; timer++) {       // LED's "on" at current "brightness" for delay 10ths ms.
        PORTB |= 0b00000011;                             // turn on LEDs at PB0, PB1
        for (int j=0; j<=i; j++) { PIND |= 0b0000001; }  // delay for part of a tenth ms
        PORTB &= 0b11111100;                             // turn off LEDs at PB0, PB1
        for (int j=0; j<=87 - i; j++) { PIND |= 0b0000001; } // delay for the rest of the tenth ms
      }
      delay_one_tenth_ms(delay);                         // LEDs are already off. Keep them off for off_time 10ths ms.
    }
  }
}



// This function starts the Offset Frequency audio in the Right ear through output OC1A  (using Timer 1)
//   to create a binaural beat (between Left and Right ears) for a Brainwave Element
//   (the base-frequency of 400.641Hz is already assumed to be playing in the Left ear before calling this function)
//   and blinks the LEDs at the same frequency for the Brainwave Element
//   and keeps it going for the Duration specified for the Brainwave Element
// The timing for the Right ear is done with 16-bit Timer 1 (set up for CTC Mode, toggling output on each compare)
//   Output frequency = Fclk / (2 * Prescale * (1 + OCR1A) ) = 8,000,000 / (2 * (1 + OCR1A) )


void do_brainwave_element(int index) {
    char brainChr = pgm_read_byte(&brainwaveTab[index].bwType);

    if (index != 0) {             // Assuming this isn't the first index...
      char lastChr = pgm_read_byte(&brainwaveTab[index - 1].bwType);
      do_audio_transition(lastChr, brainChr);   // transition.
    }

    OCR1A = get_freq(brainChr, 0);              // Set right ear frequency.
    int delay = get_freq(brainChr, 1);          // Get the LED on/off times.
    blink_LEDs( pgm_read_dword(&brainwaveTab[index].bwDuration), delay, delay );
    return;
}



int main(void) {

  TIMSK = 0x00;  // no Timer interrupts enabled
  DDRB = 0xFF;   // set all PortB pins as outputs
  PORTB = 0x00;  // all PORTB output pins Off

  // start up Base frequency = 400.641Hz on Left ear speaker through output OC0A (using Timer 0)
  //   8-bit Timer 0 OC0A (PB2, pin 14) is set up for CTC mode, toggling output on each compare
  //   Fclk = Clock = 8MHz
  //   Prescale = 256
  //   OCR0A = 38
  //   F = Fclk / (2 * Prescale * (1 + OCR0A) ) = 400.641Hz
  TCCR0A = 0b01000010;  // COM0A1:0=01 to toggle OC0A on Compare Match
                        // COM0B1:0=00 to disconnect OC0B
                        // bits 3:2 are unused
                        // WGM01:00=10 for CTC Mode (WGM02=0 in TCCR0B)
  TCCR0B = 0b00000100;  // FOC0A=0 (no force compare)
                        // F0C0B=0 (no force compare)
                        // bits 5:4 are unused
                        // WGM2=0 for CTC Mode (WGM01:00=10 in TCCR0A)
                        // CS02:00=100 for divide by 256 prescaler
  OCR0A = LEFT_BASE;  // to output 400.641Hz on OC0A (PB2, pin 14)

  // set up T1 to accept Offset Frequencies on Right ear speaker through OC1A (but don't actually start the Timer 1 here)
  //   16-bit Timer 1 OC1A (PB3, pin 15) is set up for CTC mode, toggling output on each compare
  //   Fclk = Clock = 8MHz
  //   Prescale = 1
  //   OCR0A = value for Beta, Alpha, Theta, or Delta (i.e., 9520, 9714, 9836, or 9928)
  //   F = Fclk / (2 * Prescale * (1 + OCR0A) )
  TCCR1A = 0b01000000;  // COM1A1:0=01 to toggle OC1A on Compare Match
                        // COM1B1:0=00 to disconnect OC1B
                        // bits 3:2 are unused
                        // WGM11:10=00 for CTC Mode (WGM13:12=01 in TCCR1B)
  TCCR1B = 0b00001001;  // ICNC1=0 (no Noise Canceller)
                        // ICES1=0 (don't care about Input Capture Edge)
                        // bit 5 is unused
                        // WGM13:12=01 for CTC Mode (WGM11:10=00 in TCCR1A)
                        // CS12:10=001 for divide by 1 prescaler
  TCCR1C = 0b00000000;  // FOC1A=0 (no Force Output Compare for Channel A)
                        // FOC1B=0 (no Force Output Compare for Channel B)
                        // bits 5:0 are unused

  // loop through entire Brainwave Table of Brainwave Elements
  //   each Brainwave Element consists of a Brainwave Type (Beta, Alpha, Theta, or Delta) and a Duration
  // Seeing the LEDs blink and hearing the binaural beats for the sequence of Brainwave Elements
  //   synchs up the user's brain to follow the sequence (hopefully it is a useful sequence)
  int j = 0;
  while (pgm_read_byte(&brainwaveTab[j].bwType) != '0') {  // '0' signifies end of table
    do_brainwave_element(j);
    j++;
  }
  
// End program without hitting a brick wall (ok so... what I had didn't help much, so I'm
// still working on other methods. When I get something good, I'll let everyone know.

  char last = pgm_read_byte(&brainwaveTab[j-1].bwType);
  do_LED_fade(get_freq(last,1));


  // Shut down everything and put the CPU to sleep
  TCCR0B &= 0b11111000;  // CS02:CS00=000 to stop Timer0 (turn off audio in Right ear speaker)
  TCCR1B &= 0b11111000;  // CS12:CS10=000 to stop Timer1 (turn off audio in Left ear speaker)
  MCUCR |= 0b00100000;   // SE=1 (bit 5)
  MCUCR |= 0b00010000;   // SM1:0=01 to enable Power Down Sleep Mode (bits 6, 4)
  delay_one_tenth_ms(10000);  // wait 1 second
  PORTB = 0x00;          // turn off all PORTB outputs
  DDRB = 0x00;           // make PORTB all inputs
  sleep_cpu();           // put CPU into Power Down Sleep Mode
}
