/*
 * speaker_pcm_sequencer
 *
 * modification by D. Cuartielles at iMAL, Brussels. This code will
 * use 7 sound sequences of 1000 samples each to create a sequencer.
 *
 * v0006 - release notes:
 *
 * - iMAL performance tool
 *
 * v0005 - release notes:
 *
 * - master volume control --> wish, not there
 *
 * v0004 - release notes:
 *
 * - STEREO WORKS (using channels 3 for Right and 11 for Left)
 *
 * v0003 - release notes:
 *
 * - what about STEREO? ;-) --> weird sound mixing on both channels 
 *
 * v0002 - release notes:
 *
 * - includes a tracker with the sounds to play as an array
 *
 * Plays 8-bit PCM audio on pin 11 using pulse-width modulation (PWM).
 * For Arduino with Atmega168 at 16 MHz.
 *
 * Uses two timers. The first changes the sample value 8000 times a second.
 * The second holds pin 11 high for 0-255 ticks out of a 256-tick cycle,
 * depending on sample value. The second timer repeats 62500 times per second
 * (16000000 / 256), much faster than the playback rate (8000 Hz), so
 * it almost sounds halfway decent, just really quiet on a PC speaker.
 *
 * Takes over Timer 1 (16-bit) for the 8000 Hz timer. This breaks PWM
 * (analogWrite()) for Arduino pins 9 and 10. Takes Timer 2 (8-bit)
 * for the pulse width modulation, breaking PWM for pins 11 & 3.
 *
 * References:
 *     http://www.uchobby.com/index.php/2007/11/11/arduino-sound-part-1/
 *     http://www.atmel.com/dyn/resources/prod_documents/doc2542.pdf
 *     http://www.evilmadscientist.com/article.php/avrdac
 *     http://gonium.net/md/2006/12/27/i-will-think-before-i-code/
 *     http://fly.cc.fer.hr/GDM/articles/sndmus/speaker2.html
 *     http://www.gamedev.net/reference/articles/article442.asp
 *
 * Michael Smith <michael@hurts.ca>
 */

#include <ps2.h>

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#define SAMPLE_RATE 8000

PS2 kbd(2, 4);




/*
 * The audio data needs to be unsigned, 8-bit, 8000 Hz, and small enough
 * to fit in flash. 10000-13000 samples is about the limit.
 *
 * sounddata.h should look like this:
 *     const int sounddata_length=10000;
 *     const unsigned char sounddata_data[] PROGMEM = { ..... };
 *
 * You can use wav2c from GBA CSS:
 *     http://thieumsweb.free.fr/english/gbacss.html
 * Then add "PROGMEM" in the right place. I hacked it up to dump the samples
 * as unsigned rather than signed, but it shouldn't matter.
 *
 * http://musicthing.blogspot.com/2005/05/tiny-music-makers-pt-4-mac-startup.html
 * mplayer -ao pcm macstartup.mp3
 * sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
 * sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
 * wav2c macstartup-cut.wav sounddata.h sounddata
 */

#include "sounddata.h"

int ledPin = 13;
int speakerPinL = 11;
int speakerPinR = 3;
volatile uint16_t sampleL;
volatile uint16_t sampleR;
byte lastSampleL;
byte lastSampleR;
byte currentSound = 0;
byte playingSample = false;
byte silence = '.';

byte trackerL[] = "01234...";
byte trackerR1[] = "1...1...";
byte trackerR2[] = "12..1...";
byte trackerR3[] = "1...1.1.";
byte trackerR4[] = "1...121.";
byte trackerR5[] = "5555....";
byte trackerR[] =  "01234...";



int sizeof_tracker = 8;

void kbd_init()
{
  char ack;

  kbd.write(0xff);  // send reset code
  ack = kbd.read();  // byte, kbd does self test
  ack = kbd.read();  // another ack when self test is done
}



// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {

  byte stopPlayingSample = false;
  // LEFT

  if (trackerL[currentSound] != silence) {
    if (sampleL >= sounddata_length*(trackerL[currentSound]-48 + 1)) {
      if (sampleL == sounddata_length*(trackerL[currentSound]-48 + 1) + lastSampleL) {
        stopPlayingSample = true;
      }
      else {
        // Ramp down to zero to reduce the click at the end of playback.
        OCR2A = sounddata_length*(trackerL[currentSound]-48 + 1) + lastSampleL - sampleL;
      }
    }
    else {
      OCR2A = pgm_read_byte(&sounddata_data[sampleL]);
    }
  } 
  else {
    OCR2A = 0;
    if (sampleL >= sounddata_length + lastSampleL) {
      stopPlayingSample = true;
    }
  }

  // INCREASE COUNTERS

  ++sampleL;

  if (stopPlayingSample) stopPlaybackL();
}

ISR(TIMER1_COMPB_vect) {

  byte stopPlayingSample = false;
  // RIGHT

  if (trackerR[currentSound] != silence) {
    if (sampleR >= sounddata_length*(trackerR[currentSound]-48 + 1)) {
      if (sampleR == sounddata_length*(trackerR[currentSound]-48 + 1) + lastSampleR) {
        stopPlayingSample = true;
      }
      else {
        // Ramp down to zero to reduce the click at the end of playback.
        OCR2B = sounddata_length*(trackerR[currentSound]-48 + 1) + lastSampleR - sampleR;
      }
    }
    else {
      OCR2B = pgm_read_byte(&sounddata_data[sampleR]);
    }
  } 
  else {
    OCR2B = 0;
    if (sampleR >= sounddata_length + lastSampleR) {
      stopPlayingSample = true;
    }
  }

  // INCREASE COUNTERS

  ++sampleR;

  if (stopPlayingSample) stopPlaybackR();
}

void startPlayback(int theSoundL, int theSoundR)
{
  pinMode(speakerPinL, OUTPUT);
  pinMode(speakerPinR, OUTPUT);
  playingSample = true;

  // Set up Timer 2 to do pulse width modulation on the speaker
  // pin.

  // Use internal clock (datasheet p.160)
  ASSR &= ~(_BV(EXCLK) | _BV(AS2));

  // Set fast PWM mode  (p.157)
  TCCR2A |= _BV(WGM21) | _BV(WGM20);
  TCCR2B &= ~_BV(WGM22);

  // Do non-inverting PWM on pin OC2A (p.155)
  // On the Arduino this is pin 11.
  TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
  //    TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
  TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);

  // No prescaler (p.158)
  TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

  // Set initial pulse width to the first sample.
  if (theSoundL != silence) OCR2A = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundL-48)]);
  else OCR2A = 0;
  if (theSoundR != silence) OCR2B = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundR-48)]);
  else OCR2B = 0;


  // Set up Timer 1 to send a sample every interrupt.

  cli();

  // Set CTC mode (Clear Timer on Compare Match) (p.133)
  // Have to set OCR1A *after*, otherwise it gets reset to 0!
  TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
  TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

  // No prescaler (p.134)
  TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

  // Set the compare register (OCR1A).
  // OCR1A/OCR1B are 16-bit registers, so we have to do this with
  // interrupts disabled to be safe.
  OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000
  OCR1B = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000

  // Enable interrupt when TCNT1 == OCR1A OR OCR1B (p.136)
  TIMSK1 |= _BV(OCIE1A) | _BV(OCIE1B);

  if (theSoundL != silence) {
    lastSampleL = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundL-48 + 1)-1]);
    sampleL = sounddata_length*(theSoundL-48);
  } 
  else {
    lastSampleL = 128;
    sampleL = 0;
  }
  if (theSoundR != silence) {
    lastSampleR = pgm_read_byte(&sounddata_data[sounddata_length*(theSoundR-48 + 1)-1]);
    sampleR = sounddata_length*(theSoundR-48);
  } 
  else {
    lastSampleR = 128;
    sampleR = 0;
  }
  sei();
}

void stopPlaybackL()
{
  // Disable playback per-sample interrupt.
  TIMSK1 &= ~_BV(OCIE1A);

  // Disable the per-sample timer completely.
  TCCR1B &= ~_BV(CS10);

  // Disable the PWM timer.
  TCCR2B &= ~_BV(CS10);

  digitalWrite(speakerPinL, LOW);
  playingSample = false;
}

void stopPlaybackR()
{
  // Disable playback per-sample interrupt.
  TIMSK1 &= ~_BV(OCIE1B);

  // Disable the per-sample timer completely.
  TCCR1B &= ~_BV(CS10);

  // Disable the PWM timer.
  TCCR2B &= ~_BV(CS10);

  digitalWrite(speakerPinR, LOW);
  playingSample = false;
}

void stopPlayback() {
  stopPlaybackL();
  stopPlaybackR();
}

void copySequence(int num) {
  switch (num) {
  case 1:
    for (int counter = 0; counter < sizeof_tracker; counter++) {
      trackerR[counter] = trackerR1[counter];
    }
    break;
  case 2:
    for (int counter = 0; counter < sizeof_tracker; counter++) {
      trackerR[counter] = trackerR2[counter];
    }
    break;
  case 3:
    for (int counter = 0; counter < sizeof_tracker; counter++) {
      trackerR[counter] = trackerR3[counter];
    }
    break;
  case 4:
    for (int counter = 0; counter < sizeof_tracker; counter++) {
      trackerR[counter] = trackerR4[counter];
    }
    break;
  case 5:
    for (int counter = 0; counter < sizeof_tracker; counter++) {
      trackerR[counter] = trackerR5[counter];
    }
    break;
  default:
    for (int counter = 0; counter < sizeof_tracker; counter++) {
      trackerR[counter] = silence;
    }
    break;
  }
}

void setup()
{
  pinMode(ledPin, OUTPUT);
  for (int i = 4; i <= 8; i++) {
    pinMode(i, INPUT);
    digitalWrite(i, HIGH);
  }
  Serial.begin(9600);
  kbd_init();
}

void loop()
{
  unsigned char code;
  int count = 0;

  for (;;) { /* ever */

    /* read a keycode */
    code = kbd.read();
    count++;
    /* send the data back up */
    if (code != 0xF0 && count == 1) {
      switch (code) {
      case 21:
        currentSound = 0;
        break;
      case 29:
        currentSound = 1;
        break;
      case 36:
        currentSound = 2;
        break;
      case 45:
        currentSound = 3;
        break;
      case 44:
        currentSound = 4;
        break;
      case 53:
        currentSound = 5;
        break;
      case 60:
        currentSound = 6;
        break;
      }

      startPlayback(trackerL[currentSound], trackerR[currentSound]);
      Serial.println(code, DEC);
    }
    if (count == 3) count = 0;
    //delay(20);  /* twiddle */
  }

  /*
  if (!playingSample) {

   if (Serial.available()) {
   int inByte = Serial.read();
   switch (inByte) {
   case 'L':
   for (int counter = 0; counter < sizeof_tracker; counter++) {
   while (!Serial.available()) {};
   trackerL[counter] = Serial.read();
   Serial.print(trackerL[counter]);
   }
   Serial.println();
   break;
   case 'R':
   for (int counter = 0; counter < sizeof_tracker; counter++) {
   while (!Serial.available()) {};
   trackerR[counter] = Serial.read();
   Serial.print(trackerR[counter]);
   }
   Serial.println();
   break;
   case '1':
   copySequence(1);
   break;
   case '2':
   copySequence(2);
   break;
   case '3':
   copySequence(3);
   break;
   case '4':
   copySequence(4);
   break;
   case '5':
   copySequence(5);
   break;
   case '+':
   break;
   case '-':
   break;
   case 's':
   for (int counter = 0; counter < sizeof_tracker; counter++) trackerL[counter] = silence;
   for (int counter = 0; counter < sizeof_tracker; counter++) trackerR[counter] = silence;
   break;
   default:
   break;
   }
   }

   if (!digitalRead(4)) {
   copySequence(1);
   } else
   if (!digitalRead(5)) {
   copySequence(2);
   } else
   if (!digitalRead(6)) {
   copySequence(3);
   } else
   if (!digitalRead(7)) {
   copySequence(4);
   } else
   if (!digitalRead(8)) {
   copySequence(5);
   }

   if (!digitalRead(4) && !digitalRead(5)) copySequence(0);  // go silent

   currentSound++;
   currentSound%=sizeof_tracker;
   //Serial.println(currentSound,DEC);
   //if (tracker[currentSound] != '.')
   startPlayback(trackerL[currentSound], trackerR[currentSound]);
   //else 
   //  startPlayback(-1);
   }*/
}