// Copyright 2012 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.

#include <stm32f10x_conf.h>

#include <algorithm>

#include "stmlib/utils/dsp.h"
#include "stmlib/utils/ring_buffer.h"
#include "stmlib/system/system_clock.h"
#include "stmlib/system/uid.h"

#include "braids/drivers/adc.h"
#include "braids/drivers/dac.h"
#include "braids/drivers/debug_pin.h"
#include "braids/drivers/gate_input.h"
#include "braids/drivers/internal_adc.h"
#include "braids/drivers/system.h"
#include "braids/envelope.h"
#include "braids/macro_oscillator.h"
#include "braids/quantizer.h"
#include "braids/signature_waveshaper.h"
#include "braids/vco_jitter_source.h"
#include "braids/ui.h"

#include "braids/quantizer_scales.h"

// #define PROFILE_RENDER 1

using namespace braids;
using namespace std;
using namespace stmlib;


const size_t kNumBlocks = 4;
const size_t kBlockSize = 24;

MacroOscillator osc;
Envelope envelope;
Adc adc;
Dac dac;
DebugPin debug_pin;
GateInput gate_input;
InternalAdc internal_adc;
Quantizer quantizer;
SignatureWaveshaper ws;
System sys;
VcoJitterSource jitter_source;
Ui ui;

uint8_t current_scale = 0xff;
size_t current_sample;
volatile size_t playback_block;
volatile size_t render_block;
int16_t audio_samples[kNumBlocks][kBlockSize];
uint8_t sync_samples[kNumBlocks][kBlockSize];

bool trigger_detected_flag;
volatile bool trigger_flag;
uint16_t trigger_delay;

extern "C" {
  
void HardFault_Handler(void) { while (1); }
void MemManage_Handler(void) { while (1); }
void BusFault_Handler(void) { while (1); }
void UsageFault_Handler(void) { while (1); }
void NMI_Handler(void) { }
void SVC_Handler(void) { }
void DebugMon_Handler(void) { }
void PendSV_Handler(void) { }

}

extern "C" {

void SysTick_Handler() {
  ui.Poll();
}

void TIM1_UP_IRQHandler(void) {
  if (!(TIM1->SR & TIM_IT_Update)) {
    return;
  }
  TIM1->SR = (uint16_t)~TIM_IT_Update;
  
  dac.Write(-audio_samples[playback_block][current_sample] + 32768);

  bool trigger_detected = gate_input.raised();
  sync_samples[playback_block][current_sample] = trigger_detected;
  trigger_detected_flag = trigger_detected_flag | trigger_detected;
  
  current_sample = current_sample + 1;
  if (current_sample >= kBlockSize) {
    current_sample = 0;
    playback_block = (playback_block + 1) % kNumBlocks;
  }
  
  bool adc_scan_cycle_complete = adc.PipelinedScan();
  if (adc_scan_cycle_complete) {
    ui.UpdateCv(adc.channel(0), adc.channel(1), adc.channel(2), adc.channel(3));
    if (trigger_detected_flag) {
      trigger_delay = settings.trig_delay()
          ? (1 << settings.trig_delay()) : 0;
      ++trigger_delay;
      trigger_detected_flag = false;
    }
    if (trigger_delay) {
      --trigger_delay;
      if (trigger_delay == 0) {
        trigger_flag = true;
      }
    }
  }
}

}

void Init() {
  sys.Init(F_CPU / 96000 - 1, true);
  settings.Init();
  ui.Init();
  system_clock.Init();
  adc.Init(false);
  gate_input.Init();
#ifdef PROFILE_RENDER
  debug_pin.Init();
#endif
  dac.Init();
  osc.Init();
  quantizer.Init();
  internal_adc.Init();
  
  for (size_t i = 0; i < kNumBlocks; ++i) {
    fill(&audio_samples[i][0], &audio_samples[i][kBlockSize], 0);
    fill(&sync_samples[i][0], &sync_samples[i][kBlockSize], 0);
  }
  playback_block = kNumBlocks / 2;
  render_block = 0;
  current_sample = 0;
  
  envelope.Init();
  ws.Init(GetUniqueId(1));
  jitter_source.Init();
  sys.StartTimers();
}

const uint16_t bit_reduction_masks[] = {
    0xc000,
    0xe000,
    0xf000,
    0xf800,
    0xff00,
    0xfff0,
    0xffff };

const uint16_t decimation_factors[] = { 24, 12, 6, 4, 3, 2, 1 };

void RenderBlock() {
  static int16_t previous_pitch = 0;
  static int16_t previous_shape = 0;
  static uint16_t gain_lp;

#ifdef PROFILE_RENDER
  debug_pin.High();
#endif
  envelope.Update(
      settings.GetValue(SETTING_AD_ATTACK) * 8,
      settings.GetValue(SETTING_AD_DECAY) * 8);
  uint32_t ad_value = envelope.Render();
  
  if (ui.paques()) {
    osc.set_shape(MACRO_OSC_SHAPE_QUESTION_MARK);
  } else if (settings.meta_modulation()) {
    int16_t shape = adc.channel(3);
    shape -= settings.data().fm_cv_offset;
    if (shape > previous_shape + 2 || shape < previous_shape - 2) {
      previous_shape = shape;
    } else {
      shape = previous_shape;
    }
    shape = MACRO_OSC_SHAPE_LAST * shape >> 11;
    shape += settings.shape();
    if (shape >= MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META) {
      shape = MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META;
    } else if (shape <= 0) {
      shape = 0;
    }
    MacroOscillatorShape osc_shape = static_cast<MacroOscillatorShape>(shape);
    osc.set_shape(osc_shape);
    ui.set_meta_shape(osc_shape);
  } else {
    osc.set_shape(settings.shape());
  }
  
  // Set timbre and color: CV + internal modulation.
  uint16_t parameters[2];
  for (uint16_t i = 0; i < 2; ++i) {
    int32_t value = settings.adc_to_parameter(i, adc.channel(i));
    Setting ad_mod_setting = i == 0 ? SETTING_AD_TIMBRE : SETTING_AD_COLOR;
    value += ad_value * settings.GetValue(ad_mod_setting) >> 5;
    CONSTRAIN(value, 0, 32767);
    parameters[i] = value;
  }
  osc.set_parameters(parameters[0], parameters[1]);
  
  // Apply hysteresis to ADC reading to prevent a single bit error to move
  // the quantized pitch up and down the quantization boundary.
  int32_t pitch = quantizer.Process(
      settings.adc_to_pitch(adc.channel(2)),
      (60 + settings.quantizer_root()) << 7);
  if (!settings.meta_modulation()) {
    pitch += settings.adc_to_fm(adc.channel(3));
  }
  // Check if the pitch has changed to cause an auto-retrigger
  int32_t pitch_delta = pitch - previous_pitch;
  if (settings.data().auto_trig &&
      (pitch_delta >= 0x40 || -pitch_delta >= 0x40)) {
    trigger_detected_flag = true;
  }
  previous_pitch = pitch;
  
  pitch += jitter_source.Render(settings.vco_drift());
  pitch += internal_adc.value() >> 8;
  pitch += ad_value * settings.GetValue(SETTING_AD_FM) >> 7;
  
  if (pitch > 16383) {
    pitch = 16383;
  } else if (pitch < 0) {
    pitch = 0;
  }
  
  if (settings.vco_flatten()) {
    pitch = Interpolate88(lut_vco_detune, pitch << 2);
  }
  osc.set_pitch(pitch + settings.pitch_transposition());

  if (trigger_flag) {
    osc.Strike();
    envelope.Trigger(ENV_SEGMENT_ATTACK);
    ui.StepMarquee();
    trigger_flag = false;
  }
  
  uint8_t* sync_buffer = sync_samples[render_block];
  int16_t* render_buffer = audio_samples[render_block];
  
  if (settings.GetValue(SETTING_AD_VCA) != 0
    || settings.GetValue(SETTING_AD_TIMBRE) != 0
    || settings.GetValue(SETTING_AD_COLOR) != 0
    || settings.GetValue(SETTING_AD_FM) != 0) {
    memset(sync_buffer, 0, kBlockSize);
  }
  
  osc.Render(sync_buffer, render_buffer, kBlockSize);
  
  // Copy to DAC buffer with sample rate and bit reduction applied.
  int16_t sample = 0;
  size_t decimation_factor = decimation_factors[settings.data().sample_rate];
  uint16_t bit_mask = bit_reduction_masks[settings.data().resolution];
  int32_t gain = settings.GetValue(SETTING_AD_VCA) ? ad_value : 65535;
  uint16_t signature = settings.signature() * settings.signature() * 4095;
  for (size_t i = 0; i < kBlockSize; ++i) {
    if ((i % decimation_factor) == 0) {
      sample = render_buffer[i] & bit_mask;
    }
    sample = sample * gain_lp >> 16;
    gain_lp += (gain - gain_lp) >> 4;
    int16_t warped = ws.Transform(sample);
    render_buffer[i] = Mix(sample, warped, signature);
  }
  render_block = (render_block + 1) % kNumBlocks;
#ifdef PROFILE_RENDER
  debug_pin.Low();
#endif
}

int main(void) {
  Init();
  while (1) {
    if (current_scale != settings.GetValue(SETTING_QUANTIZER_SCALE)) {
      current_scale = settings.GetValue(SETTING_QUANTIZER_SCALE);
      quantizer.Configure(scales[current_scale]);
    }
    while (render_block != playback_block) {
      RenderBlock();
    }
    ui.DoEvents();
  }
}
