// Copyright 2012 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------------
//
// Digital oscillator generated from a timer.

#include "edges/digital_oscillator.h"

#include "avrlibx/utils/op.h"

#include "edges/audio_buffer.h"
#include "edges/resources.h"

namespace edges {

static const uint8_t kMaxZone = 7;
static const int16_t kOctave = 12 * 128;
static const int16_t kPitchTableStart = 116 * 128;
  
using namespace avrlibx;

#define UPDATE_PHASE \
  phase = U24Add(phase, phase_increment);

#define BEGIN_SAMPLE_LOOP \
  uint24_t phase; \
  uint24_t phase_increment; \
  phase_increment.integral = phase_increment_.integral; \
  phase_increment.fractional = phase_increment_.fractional; \
  phase.integral = phase_.integral; \
  phase.fractional = phase_.fractional; \
  uint8_t size = kAudioBlockSize; \
  while (size--) {

#define END_SAMPLE_LOOP \
  } \
  phase_.integral = phase.integral; \
  phase_.fractional = phase.fractional;

void DigitalOscillator::ComputePhaseIncrement() {
  int16_t ref_pitch = pitch_ - kPitchTableStart;
  uint8_t num_shifts = shape_ >= OSC_PITCHED_NOISE ? 0 : 1;
  while (ref_pitch < 0) {
    ref_pitch += kOctave;
    ++num_shifts;
  }
  uint24_t increment;
  uint16_t pitch_lookup_index_integral = U16ShiftRight4(ref_pitch);
  uint8_t pitch_lookup_index_fractional = U8ShiftLeft4(ref_pitch);
  uint16_t increment16 = ResourcesManager::Lookup<uint16_t, uint16_t>(
      lut_res_oscillator_increments, pitch_lookup_index_integral);
  uint16_t increment16_next = ResourcesManager::Lookup<uint16_t, uint16_t>(
      lut_res_oscillator_increments, pitch_lookup_index_integral + 1);
  increment.integral = increment16 + U16U8MulShift8(
      increment16_next - increment16, pitch_lookup_index_fractional);
  increment.fractional = 0;
  while (num_shifts--) {
    increment = U24ShiftRight(increment);
  }
  
  note_ = U15ShiftRight7(pitch_);
  if (note_ < 12) {
    note_ = 12;
  }
  phase_increment_ = increment;
}

void DigitalOscillator::Render() {
  if (gate_) {
    ComputePhaseIncrement();
  }
  while (audio_buffer.writable() >= kAudioBlockSize) {
    if (!gate_) {
      RenderSilence();
    } else {
      RenderFn fn;
      ResourcesManager::Load(fn_table_, shape_, &fn);
      (this->*fn)();
    }
  }
}

static inline uint8_t InterpolateSample(
    const prog_uint8_t* table,
    uint16_t phase) {
  uint8_t result;
  uint8_t work;
  asm(
    "movw r30, %A2"           "\n\t"  // copy base address to r30:r31
    "mov %1, %A3"             "\n\t"  // duplicate phase increment
    "add %1, %A3"             "\n\t"  // duplicate
    "adc r30, %B3"            "\n\t"  // duplicate
    "adc r31, r1"             "\n\t"  // duplicate
    "add r30, %B3"            "\n\t"  // duplicate
    "adc r31, r1"             "\n\t"  // duplicate
    "lpm %0, z+"              "\n\t"  // load sample[n]
    "lpm r1, z+"              "\n\t"  // load sample[n+1]
    "mul %1, r1"              "\n\t"  // multiply second sample by phaseL
    "movw r30, r0"            "\n\t"  // result to accumulator
    "com %1"                  "\n\t"  // 255 - phaseL -> phaseL
    "mul %1, %0"              "\n\t"  // multiply first sample by phaseL
    "add r30, r0"             "\n\t"  // accumulate L
    "adc r31, r1"             "\n\t"  // accumulate H
    "eor r1, r1"              "\n\t"  // reset r1 after multiplication
    "mov %0, r31"             "\n\t"  // use sum H as output
    : "=r" (result), "=r" (work)
    : "r" (table), "r" (phase)
    : "r30", "r31"
  );
  return result;
}

static inline uint16_t InterpolateSample16(
    const prog_uint8_t* table,
    uint16_t phase) {
  uint16_t result;
  uint8_t work;
  asm(
    "movw r30, %A2"           "\n\t"  // copy base address to r30:r31
    "mov %1, %A3"             "\n\t"  // duplicate phase increment
    "add %1, %A3"             "\n\t"  // duplicate
    "adc r30, %B3"            "\n\t"  // duplicate
    "adc r31, r1"             "\n\t"  // duplicate
    "add r30, %B3"            "\n\t"  // duplicate
    "adc r31, r1"             "\n\t"  // duplicate
    "lpm %0, z+"              "\n\t"  // load sample[n]
    "lpm r1, z+"              "\n\t"  // load sample[n+1]
    "mul %1, r1"              "\n\t"  // multiply second sample by phaseL
    "movw r30, r0"            "\n\t"  // result to accumulator
    "com %1"                  "\n\t"  // 255 - phaseL -> phaseL
    "mul %1, %0"              "\n\t"  // multiply first sample by phaseL
    "add r30, r0"             "\n\t"  // accumulate L
    "adc r31, r1"             "\n\t"  // accumulate H
    "eor r1, r1"              "\n\t"  // reset r1 after multiplication
    "movw %0, r30"            "\n\t"  // use sum H as output
    : "=r" (result), "=r" (work)
    : "r" (table), "r" (phase)
    : "r30", "r31"
  );
  return result;
}


static inline uint16_t InterpolateTwoTables(
    const prog_uint8_t* table_a, const prog_uint8_t* table_b,
    uint16_t phase, uint8_t gain_a, uint8_t gain_b) {
  uint16_t result = 0;
  result += U8U8Mul(InterpolateSample(table_a, phase), gain_a);
  result += U8U8Mul(InterpolateSample(table_b, phase), gain_b);
  return result;
}

void DigitalOscillator::RenderSilence() {
  uint8_t size = kAudioBlockSize;
  while (size--) {
    audio_buffer.Overwrite(2048);
  }
}

void DigitalOscillator::RenderSine() {
  uint16_t aux_phase_increment = pgm_read_word(
      lut_res_bitcrusher_increments + cv_pw_);
  BEGIN_SAMPLE_LOOP
    UPDATE_PHASE
    aux_phase_ += aux_phase_increment;
    if (aux_phase_ < aux_phase_increment || !aux_phase_increment) {
      sample_ = InterpolateSample16(
          wav_res_bandlimited_triangle_6,
          phase.integral);
    }
    audio_buffer.Overwrite(sample_ >> 4);
  END_SAMPLE_LOOP
}

void DigitalOscillator::RenderBandlimitedTriangle() {
  uint8_t balance_index = U8Swap4(note_ - 12);
  uint8_t gain_2 = balance_index & 0xf0;
  uint8_t gain_1 = ~gain_2;

  uint8_t wave_index = balance_index & 0xf;
  uint8_t base_resource_id = (shape_ == OSC_NES_TRIANGLE)
      ? WAV_RES_BANDLIMITED_NES_TRIANGLE_0
      : WAV_RES_BANDLIMITED_TRIANGLE_0;

  const prog_uint8_t* wave_1 = waveform_table[base_resource_id + wave_index];
  wave_index = U8AddClip(wave_index, 1, kMaxZone);
  const prog_uint8_t* wave_2 = waveform_table[base_resource_id + wave_index];
  
  BEGIN_SAMPLE_LOOP
    UPDATE_PHASE
    uint16_t sample = InterpolateTwoTables(
        wave_1, wave_2,
        phase.integral, gain_1, gain_2);
    audio_buffer.Overwrite(sample >> 4);
  END_SAMPLE_LOOP
}

void DigitalOscillator::RenderNoiseNES() {
  uint16_t rng_state = rng_state_;
  uint16_t sample = sample_;
  BEGIN_SAMPLE_LOOP
    phase = U24Add(phase, phase_increment);
    if (phase.integral < phase_increment.integral) {
      uint8_t tap = rng_state >> 1;
      if (shape_ == OSC_NES_NOISE_SHORT) {
        tap >>= 5;
      }
      uint8_t random_bit = (rng_state ^ tap) & 1;
      rng_state >>= 1;
      if (random_bit) {
        rng_state |= 0x4000;
        sample = 0x0300;
      } else {
        sample = 0x0cff;
      }
    }
    audio_buffer.Overwrite(sample);
  END_SAMPLE_LOOP
  rng_state_ = rng_state;
  sample_ = sample;
}

void DigitalOscillator::RenderNoise() {
  uint16_t rng_state = rng_state_;
  uint16_t sample = sample_;
  BEGIN_SAMPLE_LOOP
    phase = U24Add(phase, phase_increment);
    if (phase.integral < phase_increment.integral) {
      rng_state = (rng_state >> 1) ^ (-(rng_state & 1) & 0xb400);
      sample = rng_state & 0x0fff;
      sample = 512 + ((sample * 3) >> 2);
    }
    audio_buffer.Overwrite(sample);
  END_SAMPLE_LOOP
  rng_state_ = rng_state;
  sample_ = sample;
}

/* static */
const DigitalOscillator::RenderFn DigitalOscillator::fn_table_[] PROGMEM = {
  &DigitalOscillator::RenderBandlimitedTriangle,
  &DigitalOscillator::RenderBandlimitedTriangle,
  &DigitalOscillator::RenderNoise,
  &DigitalOscillator::RenderNoiseNES,
  &DigitalOscillator::RenderNoiseNES,
  &DigitalOscillator::RenderSine,
};

}  // namespace shruthi
