/*
   Copyright (C) 2016 Ignacio Vina (@igvina)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/


// ArdVoice: version 0.1

#include "ArdVoice.h"

//#define SAMPLES 180
// 7812,5 ticks/s vs 8000 Hz -> 175,78

#define SAMPLES 176

//Could be size 10 but 16 is faster for % op
#define BUFFER_SIZE 16
uint8_t soundBuffer[BUFFER_SIZE]; 

const uint8_t *voiceName;
//MAX coeffs 10
uint8_t numberOfCoeffs;
int16_t coeffs[10];

uint8_t samplesMod;

int16_t gain;
uint8_t pitchPeriod;

uint8_t sample_count = 1;
uint8_t sample1 = 0xFF;

   
int16_t biasOffset;
uint16_t beat;
uint16_t beat_lenght;  


ArdVoice::ArdVoice(){};

void ArdVoice::playVoice(const uint8_t *audio){
  playVoice(audio, 0, 0, 1.0);
}

void ArdVoice::playVoice(const uint8_t *audio, uint16_t startTime, uint16_t endTime, float speed){


  if (!isSoundInit){
    isSoundInit = true;
/*
  The Arduboy2 library, or equivalent, will control the mode of the
  speaker pins itself, in order to handle muting.
  If the ArdVoice library is used where another library or sketch doesn't
  control the speaker pins, uncomment the following two lines.
*/
//    pinMode(PIN_SPEAKER_1, OUTPUT);
//    pinMode(PIN_SPEAKER_2, OUTPUT);

    TCCR4A = 0b01000010;    // Fast-PWM 8-bit
    TCCR4B = 0b00000001;    // 62500Hz
    // It is faster, but generates an annoying noise/buzz
    //TCCR4B = 0b00000010;    // 62500Hz / 4
    OCR4C  = 0xFF;          // Resolution to 8-bit (TOP=0xFF)
    OCR4A = 127;
#ifdef AB_ALTERNATE_WIRING
    TCCR4C = 0b01000101;
    OCR4D  = 127;
#endif  
  }

  voiceName = audio;

  byte readedByte = pgm_read_byte(&voiceName[1]);
    
  uint16_t chunks = ((readedByte & 0x0F) << 8) | (pgm_read_byte(&voiceName[0]) & 0xFF);

  numberOfCoeffs = (readedByte >> 4) & 0x0F;
  beat = (startTime * 2/*8*/) / /*180*/45;
  sample1=0;
  sample_count=1;
  samplesMod = (uint8_t)(SAMPLES * speed);
  beat_lenght =  endTime == 0 ?  chunks :  (endTime * 2/*8*/) / /*180*/45;

  //Init timer
  TIMSK4 = 0b00000100; 
}


//Timer
ISR(TIMER4_OVF_vect){
  
  sample_count--;
  
  if(sample1 != 0xFF){          
    if(sample_count == 0){
      // Should be sample_count = 8, but it's slow to process
      sample_count = 4; 
      // Read LPC coeffs on first sample
      if (sample1 == 0){
        // Reset buffer
        memset(soundBuffer, 127, BUFFER_SIZE);
        // Get array offset
        uint16_t offset = 2 + beat * (2 + numberOfCoeffs);
        beat++;

        // Check if voice ended
        if(beat > beat_lenght){
          // Stop timer and return
          OCR4A = 127;
#ifdef AB_ALTERNATE_WIRING
          OCR4D = 127;
#endif          
          TIMSK4 = 0;   
          sample1 = 0xFF;
          return;
      }

      byte readedByte = pgm_read_byte(&voiceName[offset]);
    
      pitchPeriod = readedByte & 0xb10000000; 
    
      if (pitchPeriod == 0){
        pitchPeriod = (readedByte & 0xFF) + 20;
      } else {
        pitchPeriod = 0;
      }
      
      gain = ((pgm_read_byte(&voiceName[offset+1])& 0xFF));; 
      biasOffset = 64;
      
      for (uint8_t  i = 0; i < numberOfCoeffs ; i++){
        readedByte = pgm_read_byte(&voiceName[offset + 2 + i]);
      
        if ((readedByte & 0b10000000) == 0){
          coeffs[i] =((readedByte & 0xFF) - 64);
        } else{
          coeffs[i] = (((64 * 64) / ((readedByte & 0b01111111) - 64 )));
        }  
        //TIP: Faster to precalculate
        biasOffset += coeffs[i];
      }
      biasOffset *= 127;
    }

    // Get next sample
    
    int16_t temp = pitchPeriod == 0 ? 
      gain * ((fastRand8())-127):
      (sample1 % pitchPeriod) == 0 ? gain * 127: 0;

    uint8_t sampleOffset = sample1 % BUFFER_SIZE;
 
    for (uint8_t i = 0; i < numberOfCoeffs; i++) {
      temp -= coeffs[i] * ((soundBuffer[(sampleOffset- i + BUFFER_SIZE - 1)%BUFFER_SIZE] & 0xFF));
    }
      
    temp = (temp + biasOffset) / 64;
    // Fix out of range values
    temp = temp < 0 ? 0 : temp > 255 ? 255 : temp; 

    OCR4A = soundBuffer[sampleOffset] = temp & 0xFF;
#ifdef AB_ALTERNATE_WIRING
      OCR4D = soundBuffer[sampleOffset];
#endif          
    sample1++;

    // Jump to next beat
    if (sample1 == samplesMod) sample1 = 0;
    }
  }
} 

void  ArdVoice::stopVoice(){
  OCR4A = 127;
#ifdef AB_ALTERNATE_WIRING
  OCR4D = 127;
#endif          
  TIMSK4 = 0;   
  sample1 = 0xFF;
}

boolean  ArdVoice::isVoicePlaying(){
  return sample1 != 0xFF;
}

uint8_t fastRand8() {
  static uint8_t state[STATE_BYTES] = { 0x87, 0xdd, 0xdc, 0x10, 0x35, 0xbc, 0x5c };
  static uint16_t c = 0x42;
  static unsigned int i = 0;
  uint16_t t;
  uint8_t x;

  x = state[i];
  t = (uint16_t)x * MULT_LO + c;
  c = t >> 8;

#if MULT_HI
  c += x;
#endif

  x = t & 255;
  state[i] = x;
  if (++i >= sizeof(state))
    i = 0;
  return x;
}


