// ------------------------------------------------------------------------
// audiofx_amplitude.cpp: Amplitude effects and dynamic processors.
// Copyright (C) 1999-2000,2003,2008,2009,2012 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 3 (see Ecasound Programmer's Guide)
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#include <cmath>

#include <kvu_message_item.h>
#include <kvu_dbc.h>

#include "samplebuffer_iterators.h"
#include "audiofx_amplitude.h"

#include "eca-logger.h"
#include "eca-error.h"

template<class T>
static void priv_resize_buffer(std::vector<T> *buffer, int count)
{
  buffer->resize(count);
  for(int i = 0; i < count; i++)
    (*buffer)[i] = 0.0;
}

EFFECT_AMPLITUDE::~EFFECT_AMPLITUDE(void)
{
}

EFFECT_AMPLITUDE::parameter_t EFFECT_AMPLITUDE::db_to_linear(parameter_t value)
{
  return std::pow(10.0f, static_cast<float>(value * 0.05f));
}

void EFFECT_AMPLITUDE::init(SAMPLE_BUFFER* sbuf)
{
  cur_sbuf_repp = sbuf;
  EFFECT_BASE::init(sbuf);
}

void EFFECT_AMPLITUDE::release(void)
{
  cur_sbuf_repp = 0;
  EFFECT_BASE::release();
}

EFFECT_AMPLIFY::EFFECT_AMPLIFY (EFFECT_AMPLITUDE::parameter_t multiplier_percent)
{
  set_parameter(1, multiplier_percent);
}

EFFECT_AMPLIFY::~EFFECT_AMPLIFY(void)
{
}

void EFFECT_AMPLIFY::set_parameter(int param, parameter_t value)
{
  switch (param) {
  case 1: 
    gain_rep = value / 100.0;
    break;
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_AMPLIFY::get_parameter(int param) const
{
  switch (param) {
  case 1: 
    return gain_rep * 100.0;
  }
  return 0.0;
}

void EFFECT_AMPLIFY::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  OPERATOR::parameter_description(param, pd);
}

void EFFECT_AMPLIFY::init(SAMPLE_BUFFER* sbuf)
{
  i.init(sbuf);
  sbuf_repp = sbuf;
}

void EFFECT_AMPLIFY::release(void)
{
  sbuf_repp = 0;
}

void EFFECT_AMPLIFY::process(void)
{
  sbuf_repp->multiply_by(gain_rep);
}

/**
 * Unoptimized version of process().
 */
void EFFECT_AMPLIFY::process_ref(void)
{
  i.begin();
  while(!i.end()) {
    *i.current() = *i.current() *  gain_rep;
    i.next();
  }
}

EFFECT_AMPLIFY_DB::EFFECT_AMPLIFY_DB(parameter_t gain, int channel)
  : channel_rep(-1),
    sbuf_repp(0) 
{
  set_parameter(1, gain);
  set_parameter(2, channel);
}

EFFECT_AMPLIFY_DB::~EFFECT_AMPLIFY_DB(void)
{
}

void EFFECT_AMPLIFY_DB::set_parameter(int param, parameter_t value)
{
  switch (param) {
  case 1: 
    gain_rep = EFFECT_AMPLITUDE::db_to_linear(value);
    gain_db_rep = value;
    break;

  case 2: 
    {
      int ch = static_cast<int>(value);
      /* note: ch==0 -> apply to all channels */
      if (ch >= 0) {
	bool reinit = false;
	if (channel_rep != ch &&
	    sbuf_repp != 0) {
	  reinit = true;
	}
	channel_rep = ch;
	/* note: must be done after 'channel_rep' is set */
	if (reinit == true)
	  init(sbuf_repp);
      }
    }
    break;

  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_AMPLIFY_DB::get_parameter(int param) const
{
  switch (param) {
  case 1: 
    return gain_db_rep;

  case 2:
    return channel_rep;
  }
  DBC_NEVER_REACHED();
  return 0.0;
}

void EFFECT_AMPLIFY_DB::init(SAMPLE_BUFFER* sbuf)
{
  sbuf_repp = sbuf;
  if (channel_rep > 0) {
    i_ch.init(sbuf, channel_rep - 1);
  }
  else {
    i_all.init(sbuf);
  }
  EFFECT_BASE::init(sbuf);
}

void EFFECT_AMPLIFY_DB::release(void)
{
  sbuf_repp = 0;
}

void EFFECT_AMPLIFY_DB::process(void)
{
  if (channel_rep > 0 && channel_rep <= channels()) {
    sbuf_repp->multiply_by(gain_rep, channel_rep - 1);
  }  
  else {
    sbuf_repp->multiply_by(gain_rep);
  }
}

/**
 * Unoptimized version of process().
 */
void EFFECT_AMPLIFY_DB::process_ref(void)
{
  if (channel_rep > 0 && channel_rep < channels()) {
    i_ch.begin();
    while(!i_ch.end()) {
      *i_ch.current() *= gain_rep;
      i_ch.next();
    }
  }  
  else {
    i_all.begin();
    while(!i_all.end()) {
      *i_all.current() *= gain_rep;
      i_all.next();
    }
  }
}

int EFFECT_AMPLIFY_DB::output_channels(int i_channels) const
{
  if (channel_rep > i_channels)
    return channel_rep;

  return i_channels;
}

EFFECT_AMPLIFY_CLIPCOUNT::EFFECT_AMPLIFY_CLIPCOUNT (parameter_t multiplier_percent, int max_clipped) {
  set_parameter(1, multiplier_percent);
  set_parameter(2, max_clipped);
  num_of_clipped = 0;
}

void EFFECT_AMPLIFY_CLIPCOUNT::set_parameter(int param, parameter_t value)
{
  switch (param) {
  case 1: 
    gain = value / 100.0;
    break;
  case 2:
    maxnum_of_clipped = (int)value;
    break;
  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_AMPLIFY_CLIPCOUNT::get_parameter(int param) const
{
  switch (param) {
  case 1: 
    return gain * 100.0;
  case 2:
    return maxnum_of_clipped;
  }
  DBC_NEVER_REACHED();
  return 0.0f;
}

void EFFECT_AMPLIFY_CLIPCOUNT::init(SAMPLE_BUFFER* sbuf) { i.init(sbuf); }

void EFFECT_AMPLIFY_CLIPCOUNT::process(void)
{
  i.begin();
  while(!i.end()) {
    *i.current() = *i.current() *  gain;
    if (*i.current() > SAMPLE_SPECS::impl_max_value ||
	*i.current() < SAMPLE_SPECS::impl_min_value) {
      num_of_clipped++;
    }
    else {
      num_of_clipped = 0;
    }
    i.next();
  }

  if (num_of_clipped > maxnum_of_clipped && maxnum_of_clipped != 0) {
    MESSAGE_ITEM otemp;
    otemp.setprecision(0);
    otemp << "(audiofx_amplitude) WARNING! Signal is clipping! ";
    otemp << num_of_clipped;
    otemp << " consecutive clipped samples.";
    ECA_LOG_MSG(ECA_LOGGER::info, otemp.to_string());
  }
}

void EFFECT_AMPLIFY_CLIPCOUNT::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  OPERATOR::parameter_description(param, pd);
}

EFFECT_AMPLIFY_CHANNEL::EFFECT_AMPLIFY_CHANNEL (parameter_t multiplier_percent, int channel)
{
  set_parameter(1, multiplier_percent);
  set_parameter(2, channel);
}

int EFFECT_AMPLIFY_CHANNEL::output_channels(int i_channels) const
{
  if (channel_rep + 1 > i_channels)
    return channel_rep + 1;

  return i_channels;
}

void EFFECT_AMPLIFY_CHANNEL::set_parameter(int param, parameter_t value)
{
  switch (param) {
    case 1: 
      gain = value / 100.0;
      break;
      
    case 2: 
      channel_rep = static_cast<int>(value);
      channel_rep--;
      break;
  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_AMPLIFY_CHANNEL::get_parameter(int param) const
{ 
  switch (param) {
  case 1: 
    return gain * 100.0;

  case 2: 
    return static_cast<parameter_t>(channel_rep + 1);
  }
  DBC_NEVER_REACHED();
  return 0.0;
}

void EFFECT_AMPLIFY_CHANNEL::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  OPERATOR::parameter_description(param, pd);

  switch (param) {
  case 2: 
    {
      pd->default_value = 1.0f;
      pd->bounded_above = false;
      pd->bounded_below = true;
      pd->lower_bound = 1.0f;
      pd->integer = true;
    }
  }
}

void EFFECT_AMPLIFY_CHANNEL::init(SAMPLE_BUFFER *insample)
{
  i.init(insample);
  EFFECT_AMPLITUDE::init(insample);
}

void EFFECT_AMPLIFY_CHANNEL::process(void)
{
  if (channel_rep >= 0 && channel_rep < channels()) {
    cur_sbuf_repp->multiply_by(gain, channel_rep);
  }
}

/**
 * Unoptimized version of process().
 */
void EFFECT_AMPLIFY_CHANNEL::process_ref(void)
{
  if (channel_rep >= 0 && channel_rep < channels()) {
    i.begin(channel_rep);
    while(!i.end()) {
      *i.current() = *i.current() * gain;
      i.next();
    }
  }
}

EFFECT_LIMITER::EFFECT_LIMITER (parameter_t limiting_percent)
{
  set_parameter(1, limiting_percent);
}

EFFECT_LIMITER::~EFFECT_LIMITER(void)
{
}

void EFFECT_LIMITER::set_parameter(int param, parameter_t value) {
  switch (param) {
  case 1:
    limit_rep = value / 100.0;
    break;
  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_LIMITER::get_parameter(int param) const { 
  switch (param) {
  case 1: 
    return limit_rep * 100.0;
  }
  DBC_NEVER_REACHED();
  return 0.0;
}

void EFFECT_LIMITER::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  OPERATOR::parameter_description(param, pd);
}

void EFFECT_LIMITER::init(SAMPLE_BUFFER* sbuf) { i.init(sbuf); }

void EFFECT_LIMITER::process(void) {
  i.begin();
  while(!i.end()) {
    if (*i.current() < 0) {
      if ((-(*i.current())) > limit_rep) 
	*i.current() = -limit_rep;
    }
    else {
      if (*i.current() > limit_rep)
	*i.current() = limit_rep;
     
    }
    i.next();
  }
}

EFFECT_COMPRESS::EFFECT_COMPRESS (parameter_t compress_rate, parameter_t thold) {
  set_parameter(1, compress_rate);
  set_parameter(2, thold);

  first_time = true;
}

EFFECT_COMPRESS::EFFECT_COMPRESS (const EFFECT_COMPRESS& x) {
  crate = x.crate;
  threshold = x.threshold;
  delta = x.delta;
  ratio = x.ratio;
  first_time = x.first_time;

  lastin = x.lastin;
  lastout = x.lastout;
}

void EFFECT_COMPRESS::set_parameter(int param, parameter_t value) {
  switch (param) {
  case 1: 
    crate = std::pow(2.0, value / 6.0);
    break;
  case 2: 
    threshold = value / 100.0;
    break;
  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_COMPRESS::get_parameter(int param) const { 
  switch (param) {
  case 1: 
    return (log (crate)) / (log (2.0f)) * 6.0;
  case 2: 
    return threshold * 100.0;
  }
  DBC_NEVER_REACHED();
  return 0.0;
}

void EFFECT_COMPRESS::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  switch (param) {
  case 1:
    pd->default_value = 1.0f;
    pd->description = "compression-rate-dB";
    pd->bounded_above = true;
    pd->upper_bound = 99.0f;
    pd->bounded_below = true;
    pd->lower_bound = 0.0f;
    pd->toggled = false;
    pd->integer = false;
    pd->logarithmic = true;
    pd->output = false;
    break;
  case 2:
    pd->default_value = 30.0f;
    pd->description = "threshold-%";
    pd->bounded_above = true;
    pd->upper_bound = 100.0f;
    pd->bounded_below = true;
    pd->lower_bound = 0.0f;
    pd->toggled = false;
    pd->integer = false;
    pd->logarithmic = false;
    pd->output = false;
    break;
  default: 
    DBC_NEVER_REACHED();
  }
}

void EFFECT_COMPRESS::init(SAMPLE_BUFFER *insample)
{
  i.init(insample);

  set_channels(insample->number_of_channels());
  set_samples_per_second(samples_per_second());

  priv_resize_buffer<SAMPLE_SPECS::sample_t>(&lastin, insample->number_of_channels());
  priv_resize_buffer<SAMPLE_SPECS::sample_t>(&lastout, insample->number_of_channels());
} 

void EFFECT_COMPRESS::process(void)
{
  i.begin();
  while(!i.end()) {
    if (first_time) {
      first_time = false;
      lastin[i.channel()] = lastout[i.channel()] = *i.current();
    }
    else {
      if (fabs(*i.current()) > threshold) {
	delta = *i.current() - lastin[i.channel()];
	delta /= crate;
	new_value = lastin[i.channel()] + delta;
	ratio = new_value / lastin[i.channel()];
	new_value = lastout[i.channel()] * ratio;

	if (new_value > SAMPLE_SPECS::impl_max_value) new_value = SAMPLE_SPECS::impl_max_value;
	else if (new_value < SAMPLE_SPECS::impl_min_value) new_value = SAMPLE_SPECS::impl_min_value;

	lastin[i.channel()] = *i.current();
	*i.current() = lastout[i.channel()] = new_value;
      }
      else {
	lastin[i.channel()] = lastout[i.channel()] = *i.current();
      }
    }
    i.next();
  }
}

EFFECT_NOISEGATE::EFFECT_NOISEGATE (parameter_t thlevel_percent, 
				    parameter_t thtime, 
				    parameter_t a, 
				    parameter_t h, 
				    parameter_t r)
{
  // map_parameters();

  set_parameter(1, thlevel_percent);
  set_parameter(2, thtime);
  set_parameter(3, a);
  set_parameter(4, h);
  set_parameter(5, r);
}

void EFFECT_NOISEGATE::set_parameter(int param, parameter_t value) {
  switch (param) {
  case 1: 
    th_level = SAMPLE_SPECS::max_amplitude * (value / 100.0);
    break;
  case 2: 
    th_time = (value * (parameter_t)samples_per_second() / 1000.0);
    break;
  case 3: 
    atime = (value * (parameter_t)samples_per_second() / 1000.0);
    break;
  case 4: 
    htime = (value * (parameter_t)samples_per_second() / 1000.0);
    break;
  case 5: 
    rtime = (value * (parameter_t)samples_per_second() / 1000.0);
    break;
  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_NOISEGATE::get_parameter(int param) const
{ 
  switch (param) {
  case 1: 
    return th_level * 100.0 / (parameter_t)SAMPLE_SPECS::max_amplitude;
  case 2: 
    return th_time * 1000.0 / (parameter_t)samples_per_second();
  case 3: 
    return atime * 1000.0 / (parameter_t)samples_per_second();
  case 4: 
    return htime * 1000.0 / (parameter_t)samples_per_second();
  case 5: 
    return rtime * 1000.0 / (parameter_t)samples_per_second();
  }
  DBC_NEVER_REACHED();
  return 0.0;
}

void EFFECT_NOISEGATE::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  OPERATOR::parameter_description(param, pd);
}

void EFFECT_NOISEGATE::init(SAMPLE_BUFFER *insample)
{
  i.init(insample);

  set_channels(insample->number_of_channels());
  set_samples_per_second(samples_per_second());

  priv_resize_buffer<parameter_t>(&th_time_lask, insample->number_of_channels());
  priv_resize_buffer<parameter_t>(&attack_lask, insample->number_of_channels());
  priv_resize_buffer<parameter_t>(&hold_lask, insample->number_of_channels());
  priv_resize_buffer<parameter_t>(&release_lask, insample->number_of_channels());
  priv_resize_buffer<parameter_t>(&gain, insample->number_of_channels());

  ng_status.resize(insample->number_of_channels(), int(ng_waiting));
  for(size_t n = 0; n < ng_status.size(); n++)
    ng_status[n] = ng_waiting;
}

void EFFECT_NOISEGATE::process(void)
{
  i.begin();
  while(!i.end()) {
    bool below = fabs(*i.current()) <= th_level;

    switch(ng_status[i.channel()]) 
      {
      case ng_waiting: 
	// ---
	// phase 1 - waiting
	// ---
	{
	  if (below) {
	    th_time_lask[i.channel()]++;
	    if (th_time_lask[i.channel()] >= th_time) {
	      th_time_lask[i.channel()] = 0.0;
	      ng_status[i.channel()] = ng_attacking;
	      ECA_LOG_MSG(ECA_LOGGER::user_objects,"(audiofx) noisegate - from waiting to attacking");
	    }
	  }
	  else {
	    th_time_lask[i.channel()] = 0;
	  }
	  break;
	}

      case ng_attacking: 
	// ---
	// phase 2 - attack
	// ---
	{
	  if (below) {
	    attack_lask[i.channel()]++;
	    gain[i.channel()] = (1.0 - (attack_lask[i.channel()] / atime));
	    if (attack_lask[i.channel()] >= atime) {
	      attack_lask[i.channel()] = 0.0;
	      ng_status[i.channel()] = ng_active;
	      gain[i.channel()] = 0.0;
	      ECA_LOG_MSG(ECA_LOGGER::user_objects,"(audiofx) noisegate - from attack to active");
	    }
	    *i.current() = *i.current() * gain[i.channel()];
	  }
	  else {
	    attack_lask[i.channel()] = 0;
	    ng_status[i.channel()] = ng_waiting;
	    ECA_LOG_MSG(ECA_LOGGER::user_objects,"(audiofx) noisegate - from attack to waiting");
	  }
	  break;
	}

      case ng_active: 
	// ---
	// phase 3 - active
	// ---
	{
	  if (below == false) {
	    ng_status[i.channel()] = ng_holding;
	    ECA_LOG_MSG(ECA_LOGGER::user_objects,"(audiofx) noisegate - from active to holding");
	  }
	  *i.current() = *i.current() * 0.0;
	  break;
	}

      case ng_holding: 
	// ---
	// phase 4 - holding
	// ---
	{
	  if (!below) {
	    hold_lask[i.channel()]++;
	    if (hold_lask[i.channel()] >= htime) {
	      hold_lask[i.channel()] = 0.0;
	      ng_status[i.channel()] = ng_releasing;
	      ECA_LOG_MSG(ECA_LOGGER::user_objects,"(audiofx) noisegate - from holding to release");
	    }
	  }
	  *i.current() = *i.current() * 0.0;
	  break;
	}

      case ng_releasing: 
	// ---
	// phase 5 - releasing
	// ---
	{
	  release_lask[i.channel()]++;
	  gain[i.channel()] = release_lask[i.channel()] / rtime;
	  if (release_lask[i.channel()] >= rtime) {
	    release_lask[i.channel()] = 0.0;
	    ng_status[i.channel()] = ng_waiting;
	    ECA_LOG_MSG(ECA_LOGGER::user_objects,"(audiofx) noisegate - from releasing to waiting");
	  }
	  *i.current() = *i.current() * gain[i.channel()];
	  break;
	}
      }

    i.next();
  }
}

EFFECT_NORMAL_PAN::EFFECT_NORMAL_PAN (parameter_t right_percent)
{
  set_parameter(1, right_percent);
}

void EFFECT_NORMAL_PAN::set_parameter(int param, parameter_t value)
{
  switch (param) {
  case 1: 
    right_percent_rep = value;
    if (value == 50.0) {
      l_gain = r_gain = 1.0;
    }
    else if (value < 50.0) {
      l_gain = 1.0;
      r_gain = value / 50.0;
    }
    else if (value > 50.0) {
      r_gain = 1.0;
      l_gain = (100.0 - value) / 50.0;
    }
    break;
  default:
    DBC_NEVER_REACHED();
  }
}

CHAIN_OPERATOR::parameter_t EFFECT_NORMAL_PAN::get_parameter(int param) const
{
  switch (param) {
  case 1: 
    return right_percent_rep;
  }
  return 0.0;
}

void EFFECT_NORMAL_PAN::parameter_description(int param, struct PARAM_DESCRIPTION *pd) const
{
  OPERATOR::parameter_description(param, pd);

  switch (param) {
  case 1: 
    {
      pd->default_value = 50.0f;
      pd->bounded_above = true;
      pd->upper_bound = 100.0f;
      pd->bounded_below = true;
      pd->lower_bound = 0.0f;
    }
  default:
    DBC_NEVER_REACHED();
  }
}

void EFFECT_NORMAL_PAN::init(SAMPLE_BUFFER *insample)
{
  i.init(insample);
  EFFECT_AMPLITUDE::init(insample);
}

void EFFECT_NORMAL_PAN::process(void)
{
  /* to match with out_channels() */
  cur_sbuf_repp->number_of_channels(2);
 
  cur_sbuf_repp->multiply_by(l_gain, SAMPLE_SPECS::ch_left);
  cur_sbuf_repp->multiply_by(r_gain, SAMPLE_SPECS::ch_right);
}

/**
 * Unoptimized version of process().
 */
void EFFECT_NORMAL_PAN::process_ref(void)
{
  i.begin(0);
  while(!i.end()) {
    *i.current() = *i.current() * l_gain;
    i.next();
  }

  i.begin(1);
  while(!i.end()) {
    *i.current() = *i.current() * r_gain;
    i.next();
  }
}
