// Voice class, handles generation for a single voice
// Idea and parts of code lifted from Niall Moody's Polyphonic Synth Tutorial at http://www.niallmoody.com/ndcplugs/programming.htm

#include "voice.hpp"
#include "common.hpp"
#include "MIDI2Freq.h"

//----------------------------------------------------------------------------
Voice::Voice():
  m_frequency(440.0f),
  m_note(-1),
  m_velocity(0.0f),
  m_pitchBend(0.5f),
  isActive(false),
  m_filter1_env_mod(0.75f),
  m_filter1_vel_mod(0.75f),
  m_filter2_env_mod(0.75f),
  m_filter2_vel_mod(0.75f),
  m_filter1_keytrack(0.75f),
  m_filter2_keytrack(0.75f),
  m_filter_routing(k_filter_route_serial)
{
  m_lfo1.setOscMode(k_oscmode_lfo);
  m_lfo2.setOscMode(k_oscmode_lfo);
  m_lfo3.setOscMode(k_oscmode_lfo);
  m_lfo1.setWaveform(k_sine);
  m_lfo2.setWaveform(k_sine);
  m_lfo3.setWaveform(k_sine);
  m_lfo1.trigger(0.1f);
  m_lfo2.trigger(0.2f);
  m_lfo3.trigger(0.3f);
}

Voice::~Voice()
{
  noteOff();
}

//----------------------------------------------------------------------------
void Voice::noteOn(int note, float velocity)
{
  if(!isActive)
  {
    m_frequency = MIDI2Freq[note];
    m_filter_keytrack_offset_freq = m_frequency - MIDI2Freq[64];
    m_note = note;
    setPitchModMinMax();

    m_osc1.trigger(m_frequency);
    m_osc2.trigger(m_frequency);
    m_osc3.trigger(m_frequency);
    m_lfo1.setPitchMod(m_lfo1_keytrack*(static_cast<float>(note-64)/64.0f));
    m_lfo2.setPitchMod(m_lfo2_keytrack*(static_cast<float>(note-64)/64.0f));
    m_lfo3.setPitchMod(m_lfo3_keytrack*(static_cast<float>(note-64)/64.0f));
    m_lfo1.trigger();
    m_lfo2.trigger();
    m_lfo3.trigger();
    m_pitch_env.trigger();
    m_amp_env.trigger();
    m_filter_env.trigger();
    isActive = true;
    m_velocity = velocity;
  }
  else
  {
    //retrigger, need to do what we can to avoid pops
    m_frequency = MIDI2Freq[note];
    m_filter_keytrack_offset_freq = m_frequency - MIDI2Freq[64];
    m_note = note;
    setPitchModMinMax();

    m_osc1.trigger(m_frequency);
    m_osc2.trigger(m_frequency);
    m_osc3.trigger(m_frequency);
    m_lfo1.setPitchMod(m_lfo1_keytrack*(static_cast<float>(note-64)/64.0f));
    m_lfo2.setPitchMod(m_lfo2_keytrack*(static_cast<float>(note-64)/64.0f));
    m_lfo3.setPitchMod(m_lfo3_keytrack*(static_cast<float>(note-64)/64.0f));
    m_lfo1.trigger();
    m_lfo2.trigger();
    m_lfo3.trigger();
    m_pitch_env.trigger();
    m_amp_env.trigger();
    m_filter_env.trigger();
    isActive = true;
    m_velocity = velocity;
  }
}

//----------------------------------------------------------------------------
void Voice::noteOff()
{
  m_pitch_env.stop();
  m_amp_env.stop();
  m_filter_env.stop();
}

//----------------------------------------------------------------------------
void Voice::setPitchBend(float val)
{
  m_pitchBend = val;

  if(val>1.0f)
    val=1.0f;
  else if(val<0.0f)
    val=0.0f;
  //Scale pitch bend to +/- 2 semitones.
  if(val >= 0.5f)
  {
    val -= 0.5f;
    val *= 2.0f;
    m_pitch_bend = (MIDI2Freq[m_note+2]-m_frequency) * val;
  }
  else if(val < 0.5f)
  {
    val -= 0.5f;
    val *= 2.0f;
    m_pitch_bend = (m_frequency-MIDI2Freq[m_note-2]) * val;
  }
}

//----------------------------------------------------------------------------
void Voice::setPitchEnvMod(float val)
{
  //Scale pitch mod to +/- the amount of semitones set in parameters.h -> g_pitch_env_range.
  if(val >= 0.5f)
  {
    val -= 0.5f;
    val *= 2.0f;
    m_pitch_env_mod = val*(MIDI2Freq[m_note]*cpowf(1.05946309436f,static_cast<float>(g_pitch_env_range)));
    /*
       pitchval = m_note+g_pitch_env_range;
       if(pitchval>127)
       pitchval=127;
       m_pitch_env_mod = MIDI2Freq[pitchval] * val;
       */
  }
  else if(val < 0.5f)
  {
    val -= 0.5f;
    val *= 2.0f;
    m_pitch_env_mod = val*(MIDI2Freq[m_note]*cpowf(0.94387431268f,static_cast<float>(g_pitch_env_range))+MIDI2Freq[m_note]);
    //		m_pitch_env_mod = val*MIDI2Freq[m_note]*pow(1.05946309436f,g_pitch_env_range);
    /*
       pitchval = m_note-g_pitch_env_range;
       if(pitchval<0)
       pitchval=0;
       m_pitch_env_mod = MIDI2Freq[pitchval] * val;
       */
  }
}

//----------------------------------------------------------------------------
void Voice::setPitchModMinMax()
{
  /*
     int pitchval;
  //use g_pitch_env_range just like in setPitchEnvMod
  pitchval = m_note+g_pitch_env_range;
  if(pitchval>127)
  pitchval=127;
  m_pitchmod_max = MIDI2Freq[pitchval];

  pitchval = m_note-g_pitch_env_range;
  if(pitchval<0)
  pitchval=0;
  m_pitchmod_min = MIDI2Freq[pitchval];
  */
  m_pitchmod_max = MIDI2Freq[m_note]*cpowf(1.05946309436f,static_cast<float>(g_pitch_env_range))-MIDI2Freq[m_note];
  m_pitchmod_min = -1.0f*(MIDI2Freq[m_note]*cpowf(0.94387431268f,static_cast<float>(g_pitch_env_range))-MIDI2Freq[m_note]);
}

//----------------------------------------------------------------------------
void Voice::setFilterRouting(float val)
{
  int mode = common::clrintf(val*(k_num_filter_routing_modes-1));
  m_filter_routing = mode;
}

//----------------------------------------------------------------------------
void Voice::setParameter(int parameter, float value)
{
  switch(parameter)
  {
    case k_pitchbend:
      setPitchBend(value);
      break;

    case k_filter_routing:
      setFilterRouting(value);
      break;

      //osc1
    case k_osc1_waveform:
      m_osc1.setWaveform(value);
      break;

    case k_osc1_detune:
      m_osc1.setDetune(value);
      break;

    case k_osc1_semi:
      m_osc1.setSemi(value);		
      break;

    case k_osc1_volume:
      m_osc1.setVolume(value);		
      break;

    case k_osc1_pw:
      m_osc1.setPW(value);		
      break;

    case k_osc1_pwm:
      m_osc1.setPWM(value);		
      break;

      //osc2
    case k_osc2_waveform:
      m_osc2.setWaveform(value);
      break;

    case k_osc2_detune:
      m_osc2.setDetune(value);		
      break;

    case k_osc2_semi:
      m_osc2.setSemi(value);		
      break;

    case k_osc2_volume:
      m_osc2.setVolume(value);				
      break;

    case k_osc2_pw:
      m_osc2.setPW(value);		
      break;

    case k_osc2_pwm:
      m_osc2.setPWM(value);		
      break;

      //osc3
    case k_osc3_waveform:
      m_osc3.setWaveform(value);
      break;

    case k_osc3_detune:
      m_osc3.setDetune(value);		
      break;

    case k_osc3_semi:
      m_osc3.setSemi(value);		
      break;

    case k_osc3_volume:
      m_osc3.setVolume(value);				
      break;

    case k_osc3_pw:
      m_osc3.setPW(value);		
      break;

    case k_osc3_pwm:
      m_osc3.setPWM(value);		
      break;

      //all lfos
    case k_tempo:
      m_lfo1.setTempo(value);
      m_lfo2.setTempo(value);
      m_lfo3.setTempo(value);
      break;

      //lfo1
    case k_lfo1_waveform:
      m_lfo1.setWaveform(value);
      break;

    case k_lfo1_speed:
      //			m_lfo1->setPitch(value*g_lfo_max_frequency);
      m_lfo1.setPitch(value);
      break;

    case k_lfo1_startphase:
      m_lfo1.setStartphase(value);
      break;

    case k_lfo1_keytrack:
      m_lfo1_keytrack = (value-0.5f)*2.0f;
      break;

    case k_lfo1_mod_dest:
      m_lfo1_mod_dest = common::clrintf(value*k_num_mod_dests);
      break;

    case k_lfo1_mod_amount:
      m_lfo1_mod_amount = value;
      break;

      //lfo2
    case k_lfo2_waveform:
      m_lfo2.setWaveform(value);
      break;

    case k_lfo2_speed:
      //			m_lfo2->setPitch(value*g_lfo_max_frequency);
      m_lfo2.setPitch(value);
      break;

    case k_lfo2_startphase:
      m_lfo2.setStartphase(value);
      break;

    case k_lfo2_keytrack:
      m_lfo2_keytrack = (value-0.5f)*2.0f;
      break;

    case k_lfo2_mod_dest:
      m_lfo2_mod_dest = common::clrintf(value*k_num_mod_dests);
      break;

    case k_lfo2_mod_amount:
      m_lfo2_mod_amount = value;
      break;

      //lfo3
    case k_lfo3_waveform:
      m_lfo3.setWaveform(value);
      break;

    case k_lfo3_speed:
      //			m_lfo3->setPitch(value*g_lfo_max_frequency);
      m_lfo3.setPitch(value);
      break;

    case k_lfo3_startphase:
      m_lfo3.setStartphase(value);
      break;

    case k_lfo3_keytrack:
      m_lfo3_keytrack = (value-0.5f)*2.0f;
      break;

    case k_lfo3_mod_dest:
      m_lfo3_mod_dest = common::clrintf(value*k_num_mod_dests);
      break;

    case k_lfo3_mod_amount:
      m_lfo3_mod_amount = value;
      break;

      //pitch env
    case k_pitch_env_mod:
      setPitchEnvMod(value);
      break;

    case k_pitch_attack:
      m_pitch_env.setAttack(value);
      break;

    case k_pitch_decay:
      m_pitch_env.setDecay(value);
      break;

    case k_pitch_sustain:
      m_pitch_env.setSustain(value);
      break;

    case k_pitch_release:
      m_pitch_env.setRelease(value);
      break;


      //amp env
    case k_amp_attack:
      m_amp_env.setAttack(value);
      break;

    case k_amp_decay:
      m_amp_env.setDecay(value);
      break;

    case k_amp_sustain:
      m_amp_env.setSustain(value);
      break;

    case k_amp_release:
      m_amp_env.setRelease(value);
      break;

    case k_env_length:
      m_pitch_env.setLength(value);
      m_amp_env.setLength(value);
      m_filter_env.setLength(value);
      break;

      //filter 1
    case k_filter1_mode:
      m_filter1.setMode(value);
      break;

    case k_filter1_cutoff:
      m_filter1.setCutoff(value);
      break;

    case k_filter1_resonance:
      m_filter1.setResonance(value);
      break;

    case k_filter1_keytrack:
      m_filter1_keytrack = (value-0.5f)*2.0f;
      break;

    case k_filter1_env_mod:
      m_filter1_env_mod = (value-0.5f)*2.0f;
      break;

    case k_filter1_vel_mod:
      m_filter1_vel_mod = (value-0.5f)*2.0f;
      break;

    case k_filter1_drive:
      m_filter1.setDrive(value);
      break;

    case k_filter1_output_level:
      m_filter1.setOutputLevel(value);
      break;

      //filter 2
    case k_filter2_mode:
      m_filter2.setMode(value);
      break;

    case k_filter2_cutoff:
      m_filter2.setCutoff(value);
      break;

    case k_filter2_resonance:
      m_filter2.setResonance(value);
      break;

    case k_filter2_keytrack:
      m_filter2_keytrack = (value-0.5f)*2.0f;
      break;

    case k_filter2_env_mod:
      m_filter2_env_mod = (value-0.5f)*2.0f;
      break;

    case k_filter2_vel_mod:
      m_filter2_vel_mod = (value-0.5f)*2.0f;
      break;

    case k_filter2_drive:
      m_filter2.setDrive(value);
      break;

    case k_filter2_output_level:
      m_filter2.setOutputLevel(value);
      break;

      //filter envelope
    case k_filter_attack:
      m_filter_env.setAttack(value);
      break;

    case k_filter_decay:
      m_filter_env.setDecay(value);
      break;

    case k_filter_sustain:
      m_filter_env.setSustain(value);
      break;

    case k_filter_release:
      m_filter_env.setRelease(value);
      break;

    default:
      break;
  }
}

//----------------------------------------------------------------------------
float Voice::getSample()
{
  float retVal;
  float temp;

  float amp_mod = 0.0f;
  float lfopitch_mod = 0.0f;
  float lfopitch_mod_osc1 = 0.0f;
  float lfopitch_mod_osc2 = 0.0f;
  float lfopitch_mod_osc3 = 0.0f;
  float filter1_mod = 0.0f;
  float filter2_mod = 0.0f;
  //this is pretty terrible, probably should find a better way

  float lfo1val=m_lfo1.getSample();
  float lfo2val=m_lfo2.getSample();
  float lfo3val=m_lfo3.getSample();

  if(m_lfo1_mod_dest==k_mod_dst_pitch)
    lfopitch_mod += m_lfo1_mod_amount * lfo1val;
  if(m_lfo1_mod_dest==k_mod_dst_pitch_osc1)
    lfopitch_mod_osc1 += m_lfo1_mod_amount * lfo1val;
  if(m_lfo1_mod_dest==k_mod_dst_pitch_osc2)
    lfopitch_mod_osc2 += m_lfo1_mod_amount * lfo1val;
  if(m_lfo1_mod_dest==k_mod_dst_pitch_osc3)
    lfopitch_mod_osc3 += m_lfo1_mod_amount * lfo1val;

  if(m_lfo2_mod_dest==k_mod_dst_pitch)
    lfopitch_mod += m_lfo2_mod_amount * lfo2val;
  if(m_lfo2_mod_dest==k_mod_dst_pitch_osc1)
    lfopitch_mod_osc1 += m_lfo2_mod_amount * lfo2val;
  if(m_lfo2_mod_dest==k_mod_dst_pitch_osc2)
    lfopitch_mod_osc2 += m_lfo2_mod_amount * lfo2val;
  if(m_lfo2_mod_dest==k_mod_dst_pitch_osc3)
    lfopitch_mod_osc3 += m_lfo2_mod_amount * lfo2val;

  if(m_lfo3_mod_dest==k_mod_dst_pitch)
    lfopitch_mod += m_lfo3_mod_amount * lfo3val;
  if(m_lfo3_mod_dest==k_mod_dst_pitch_osc1)
    lfopitch_mod_osc1 += m_lfo1_mod_amount * lfo3val;
  if(m_lfo3_mod_dest==k_mod_dst_pitch_osc2)
    lfopitch_mod_osc2 += m_lfo1_mod_amount * lfo3val;
  if(m_lfo3_mod_dest==k_mod_dst_pitch_osc3)
    lfopitch_mod_osc3 += m_lfo1_mod_amount * lfo3val;


  lfopitch_mod = common::cclamp1f(lfopitch_mod);
  if(lfopitch_mod<0.0f)
    lfopitch_mod *= m_pitchmod_min;
  else
    lfopitch_mod *= m_pitchmod_max;

  lfopitch_mod_osc1 = common::cclamp1f(lfopitch_mod_osc1);
  if(lfopitch_mod_osc1<0.0f)
    lfopitch_mod_osc1 *= m_pitchmod_min;
  else
    lfopitch_mod_osc1 *= m_pitchmod_max;

  lfopitch_mod_osc2 = common::cclamp1f(lfopitch_mod_osc2);
  if(lfopitch_mod_osc2<0.0f)
    lfopitch_mod_osc2 *= m_pitchmod_min;
  else
    lfopitch_mod_osc2 *= m_pitchmod_max;

  lfopitch_mod_osc3 = common::cclamp1f(lfopitch_mod_osc3);
  if(lfopitch_mod_osc3<0.0f)
    lfopitch_mod_osc3 *= m_pitchmod_min;
  else
    lfopitch_mod_osc3 *= m_pitchmod_max;

  if(m_lfo1_mod_dest==k_mod_dst_amp)
    amp_mod += m_lfo1_mod_amount * lfo1val;
  if(m_lfo2_mod_dest==k_mod_dst_amp)
    amp_mod += m_lfo2_mod_amount * lfo2val;
  if(m_lfo3_mod_dest==k_mod_dst_amp)
    amp_mod += m_lfo3_mod_amount * lfo3val;

  if(m_lfo1_mod_dest==k_mod_dst_filter1_cutoff)
    filter1_mod += m_lfo1_mod_amount * lfo1val;
  if(m_lfo2_mod_dest==k_mod_dst_filter1_cutoff)
    filter1_mod += m_lfo2_mod_amount * lfo2val;
  if(m_lfo3_mod_dest==k_mod_dst_filter1_cutoff)
    filter1_mod += m_lfo3_mod_amount * lfo3val;

  if(m_lfo1_mod_dest==k_mod_dst_filter2_cutoff)
    filter2_mod += m_lfo1_mod_amount * lfo1val;
  if(m_lfo2_mod_dest==k_mod_dst_filter2_cutoff)
    filter2_mod += m_lfo2_mod_amount * lfo2val;
  if(m_lfo3_mod_dest==k_mod_dst_filter2_cutoff)
    filter2_mod += m_lfo3_mod_amount * lfo3val;

  if(m_lfo1_mod_dest==k_mod_dst_pwm)
  {
    m_osc1.setPWMod(m_lfo1_mod_amount * lfo1val);
    m_osc2.setPWMod(m_lfo1_mod_amount * lfo1val);
    m_osc3.setPWMod(m_lfo1_mod_amount * lfo1val);
  }
  if(m_lfo2_mod_dest==k_mod_dst_pwm)
  {
    m_osc1.setPWMod(m_lfo2_mod_amount * lfo2val);
    m_osc2.setPWMod(m_lfo2_mod_amount * lfo2val);
    m_osc3.setPWMod(m_lfo2_mod_amount * lfo2val);
  }
  if(m_lfo3_mod_dest==k_mod_dst_pwm)
  {
    m_osc1.setPWMod(m_lfo3_mod_amount * lfo3val);
    m_osc2.setPWMod(m_lfo3_mod_amount * lfo3val);
    m_osc3.setPWMod(m_lfo3_mod_amount * lfo3val);
  }

  // when the pitch envelope is at 0.0f the pitch should be unaltered
  float pitch_mod = lfopitch_mod + m_pitch_env_mod*m_pitch_env.getSample() + m_pitch_bend;

  m_osc1.setPitch(m_frequency+pitch_mod+lfopitch_mod_osc1);
  m_osc2.setPitch(m_frequency+pitch_mod+lfopitch_mod_osc2);
  m_osc3.setPitch(m_frequency+pitch_mod+lfopitch_mod_osc3);

  retVal = 0.33f*(m_osc1.getSample() + m_osc2.getSample() + m_osc3.getSample());
  //	retVal = common::rationalTanh(retVal);

  float filter_keytrack_mod = m_filter_keytrack_offset_freq/(0.5f*SAMPLERATE);
  //	float filter_keytrack_mod = m_filter_keytrack_offset_freq/(0.5f*44100.0f);


  //filter
  m_filter1.setMod(m_filter1_env_mod, m_filter_env.getSample());
  m_filter2.setMod(m_filter2_env_mod, m_filter_env.getSample());
  //velmod: ok
  //keytrack: ok
  //envmod: ok
  m_filter1.setStaticMod(m_filter1_vel_mod*(m_velocity)+filter_keytrack_mod*2.0f*m_filter1_keytrack+filter1_mod);
  m_filter2.setStaticMod(m_filter2_vel_mod*(m_velocity)+filter_keytrack_mod*2.0f*m_filter2_keytrack+filter2_mod);

  switch(m_filter_routing)
  {
    case k_filter_route_serial:
      retVal = m_filter1.process(retVal);
      retVal = m_filter2.process(retVal);
      break;

    case k_filter_route_parallel:
      temp = m_filter1.process(retVal);
      //TODO: add a mixing value for filter balance?
      retVal = (0.5f*temp) + (0.5f*m_filter2.process(retVal));
      break;
  }

  //amp envelope has reached the end, setting voice inactive
  if(m_amp_env.isActive()==false)
  {
    isActive=false;
    m_osc1.stop();
    m_osc2.stop();
    m_osc3.stop();
  }

  //Scale according to velocity and return.
  //	return retVal * m_velocity * m_amp_env->getSample() * (1+amp_mod);
  //	return common::clampFloat2Unit(retVal * m_velocity * m_amp_env->getSample() * (1+amp_mod));
  return ctanhf(retVal * m_velocity * m_amp_env.getSample() * (1+amp_mod));
}

