/**********************************************************************

  Audacity: A Digital Audio Editor

  ChangeTempo.cpp

  Vaughan Johnson,
  Dominic Mazzoni

*******************************************************************//**

\class EffectChangeTempo
\brief An EffectSoundTouch provides speeding up or
  slowing down tempo without changing pitch.

*//*******************************************************************/

#include "../Audacity.h" // for USE_SOUNDTOUCH

#if USE_SOUNDTOUCH

#include <math.h>

#include <wx/intl.h>

#include "../ShuttleGui.h"
#include "../widgets/valnum.h"
#include "TimeWarper.h"

#include "ChangeTempo.h"

enum
{
   ID_PercentChange = 10000,
   ID_FromBPM,
   ID_ToBPM,
   ID_FromLength,
   ID_ToLength
};

// Soundtouch is not reasonable below -99% or above 3000%.

// Define keys, defaults, minimums, and maximums for the effect parameters
//
//     Name          Type     Key               Def   Min      Max      Scale
Param( Percentage,   double,  XO("Percentage"), 0.0,  -99.0,   3000.0,  1  );

// We warp the slider to go up to 400%, but user can enter higher values.
static const double kSliderMax = 100.0;         // warped above zero to actually go up to 400%
static const double kSliderWarp = 1.30105;      // warp power takes max from 100 to 400.

//
// EffectChangeTempo
//

BEGIN_EVENT_TABLE(EffectChangeTempo, wxEvtHandler)
    EVT_TEXT(ID_PercentChange, EffectChangeTempo::OnText_PercentChange)
    EVT_SLIDER(ID_PercentChange, EffectChangeTempo::OnSlider_PercentChange)
    EVT_TEXT(ID_FromBPM, EffectChangeTempo::OnText_FromBPM)
    EVT_TEXT(ID_ToBPM, EffectChangeTempo::OnText_ToBPM)
    EVT_TEXT(ID_ToLength, EffectChangeTempo::OnText_ToLength)
END_EVENT_TABLE()

EffectChangeTempo::EffectChangeTempo()
{
   m_PercentChange = DEF_Percentage;
   m_FromBPM = 0.0; // indicates not yet set
   m_ToBPM = 0.0; // indicates not yet set
   m_FromLength = 0.0;
   m_ToLength = 0.0;

   m_bLoopDetect = false;

   SetLinearEffectFlag(true);
}

EffectChangeTempo::~EffectChangeTempo()
{
}

// IdentInterface implementation

wxString EffectChangeTempo::GetSymbol()
{
   return CHANGETEMPO_PLUGIN_SYMBOL;
}

wxString EffectChangeTempo::GetDescription()
{
   return XO("Change the tempo of a selection without changing its pitch");
}

// EffectIdentInterface implementation

EffectType EffectChangeTempo::GetType()
{
   return EffectTypeProcess;
}

bool EffectChangeTempo::SupportsAutomation()
{
   return true;
}

// EffectClientInterface implementation

bool EffectChangeTempo::GetAutomationParameters(EffectAutomationParameters & parms)
{
   parms.Write(KEY_Percentage, m_PercentChange);

   return true;
}

bool EffectChangeTempo::SetAutomationParameters(EffectAutomationParameters & parms)
{
   ReadAndVerifyDouble(Percentage);

   m_PercentChange = Percentage;

   return true;
}

// Effect implementation

double EffectChangeTempo::CalcPreviewInputLength(double previewLength)
{
   return previewLength * (100.0 + m_PercentChange) / 100.0;
}

bool EffectChangeTempo::CheckWhetherSkipEffect()
{
   return (m_PercentChange == 0.0);
}

bool EffectChangeTempo::Init()
{
   // The selection might have changed since the last time EffectChangeTempo
   // was invoked, so recalculate the Length parameters.
   m_FromLength = mT1 - mT0;
   m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);

   mSoundTouch = NULL;

   return true;
}

bool EffectChangeTempo::Process()
{
   mSoundTouch = new SoundTouch();
   mSoundTouch->setTempoChange(m_PercentChange);
   double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0);
   SetTimeWarper(new RegionTimeWarper(mT0, mT1,
            new LinearTimeWarper(mT0, mT0, mT1, mT1Dashed )));
   bool success = EffectSoundTouch::Process();
   if( success )
      mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.);
   return success;
}

void EffectChangeTempo::PopulateOrExchange(ShuttleGui & S)
{
   S.StartVerticalLay(0);
   {
      S.AddSpace(0, 5);
      S.AddTitle(_("Change Tempo without Changing Pitch"));
      S.SetBorder(5);

      //
      S.StartMultiColumn(2, wxCENTER);
      {
         FloatingPointValidator<double> vldPercentage(3, &m_PercentChange, NUM_VAL_THREE_TRAILING_ZEROES);
         vldPercentage.SetRange(MIN_Percentage, MAX_Percentage);
         m_pTextCtrl_PercentChange = S.Id(ID_PercentChange)
            .AddTextBox(_("Percent Change:"), wxT(""), 12);
         m_pTextCtrl_PercentChange->SetValidator(vldPercentage);
      }
      S.EndMultiColumn();

      //
      S.StartHorizontalLay(wxEXPAND);
      {
         S.SetStyle(wxSL_HORIZONTAL);
         m_pSlider_PercentChange = S.Id(ID_PercentChange)
            .AddSlider(wxT(""), 0, (int)kSliderMax, (int)MIN_Percentage);
         m_pSlider_PercentChange->SetName(_("Percent Change"));
      }
      S.EndHorizontalLay();

      //
      S.StartMultiColumn(5, wxCENTER);
      {
         //
         S.AddUnits(_("Beats per minute:"));

         FloatingPointValidator<double> vldFromBPM(3, &m_FromBPM, NUM_VAL_THREE_TRAILING_ZEROES | NUM_VAL_ZERO_AS_BLANK);
         m_pTextCtrl_FromBPM = S.Id(ID_FromBPM)
            .AddTextBox(_("from"), wxT(""), 12);
         m_pTextCtrl_FromBPM->SetName(_("From beats per minute"));
         m_pTextCtrl_FromBPM->SetValidator(vldFromBPM);

         FloatingPointValidator<double> vldToBPM(3, &m_ToBPM, NUM_VAL_THREE_TRAILING_ZEROES | NUM_VAL_ZERO_AS_BLANK);
         m_pTextCtrl_ToBPM = S.Id(ID_ToBPM)
            .AddTextBox(_("to"), wxT(""), 12);
         m_pTextCtrl_ToBPM->SetName(_("To beats per minute"));
         m_pTextCtrl_ToBPM->SetValidator(vldToBPM);

         //
         S.AddUnits(_("Length (seconds):"));

         int precission = 2;
         FloatingPointValidator<double> vldFromLength(precission, &m_FromLength, NUM_VAL_TWO_TRAILING_ZEROES);
         m_pTextCtrl_FromLength = S.Id(ID_FromLength)
            .AddTextBox(_("from"), wxT(""), 12);
         m_pTextCtrl_FromLength->SetName(_("From length in seconds"));
         m_pTextCtrl_FromLength->SetValidator(vldFromLength);
         m_pTextCtrl_FromLength->Enable(false); // Disable because the value comes from the user selection.

         FloatingPointValidator<double> vldToLength(2, &m_ToLength, NUM_VAL_TWO_TRAILING_ZEROES);

         // min and max need same precision as what we're validating (bug 963)
         double minLength = (m_FromLength * 100.0) / (100.0 + MAX_Percentage);
         double maxLength = (m_FromLength * 100.0) / (100.0 + MIN_Percentage);
         minLength = Internat::CompatibleToDouble(Internat::ToString(minLength, precission));
         maxLength = Internat::CompatibleToDouble(Internat::ToString(maxLength, precission));

         vldToLength.SetRange(minLength, maxLength);
         m_pTextCtrl_ToLength = S.Id(ID_ToLength)
            .AddTextBox(_("to"), wxT(""), 12);
         m_pTextCtrl_ToLength->SetName(_("To length in seconds"));
         m_pTextCtrl_ToLength->SetValidator(vldToLength);
      }
      S.EndMultiColumn();
   }
   S.EndVerticalLay();

   return;
}

bool EffectChangeTempo::TransferDataToWindow()
{
   // Reset from length because it can be changed by Preview
   m_FromLength = mT1 - mT0;

   m_bLoopDetect = true;

   if (!mUIParent->TransferDataToWindow())
   {
      return false;
   }

   // percent change controls
   Update_Slider_PercentChange();
   Update_Text_ToBPM();
   Update_Text_ToLength();

   m_bLoopDetect = false;

   return true;
}

bool EffectChangeTempo::TransferDataFromWindow()
{
   if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
   {
      return false;
   }

   return true;
}

// handler implementations for EffectChangeTempo

void EffectChangeTempo::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
{
   if (m_bLoopDetect)
      return;

   m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow();

   m_bLoopDetect = true;
   Update_Slider_PercentChange();
   Update_Text_ToBPM();
   Update_Text_ToLength();
   m_bLoopDetect = false;
}

void EffectChangeTempo::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
{
   if (m_bLoopDetect)
      return;

   m_PercentChange = (double)(m_pSlider_PercentChange->GetValue());
   // Warp positive values to actually go up faster & further than negatives.
   if (m_PercentChange > 0.0)
      m_PercentChange = pow(m_PercentChange, kSliderWarp);

   m_bLoopDetect = true;
   Update_Text_PercentChange();
   Update_Text_ToBPM();
   Update_Text_ToLength();
   m_bLoopDetect = false;
}

void EffectChangeTempo::OnText_FromBPM(wxCommandEvent & WXUNUSED(evt))
{
   if (m_bLoopDetect)
      return;

   m_pTextCtrl_FromBPM->GetValidator()->TransferFromWindow();

   m_bLoopDetect = true;

   Update_Text_ToBPM();

   m_bLoopDetect = false;
}

void EffectChangeTempo::OnText_ToBPM(wxCommandEvent & WXUNUSED(evt))
{
   if (m_bLoopDetect)
      return;

   m_pTextCtrl_ToBPM->GetValidator()->TransferFromWindow();

   m_bLoopDetect = true;

   // If FromBPM has already been set, then there's a new percent change.
   if (m_FromBPM != 0.0 && m_ToBPM != 0.0)
   {
      m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0;

      Update_Text_PercentChange();
      Update_Slider_PercentChange();

      Update_Text_ToLength();
   }

   m_bLoopDetect = false;
}

void EffectChangeTempo::OnText_ToLength(wxCommandEvent & WXUNUSED(evt))
{
   if (m_bLoopDetect)
      return;

   m_pTextCtrl_ToLength->GetValidator()->TransferFromWindow();

   if (m_ToLength != 0.0)
   {
      m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0;
   }

   m_bLoopDetect = true;

   Update_Text_PercentChange();
   Update_Slider_PercentChange();

   Update_Text_ToBPM();

   m_bLoopDetect = false;
}

// helper fns

void EffectChangeTempo::Update_Text_PercentChange()
{
   m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
}

void EffectChangeTempo::Update_Slider_PercentChange()
{
   double unwarped = m_PercentChange;
   if (unwarped > 0.0)
      // Un-warp values above zero to actually go up to kSliderMax.
      unwarped = pow(m_PercentChange, (1.0 / kSliderWarp));

   // Add 0.5 to unwarped so trunc -> round.
   m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
}

void EffectChangeTempo::Update_Text_ToBPM()
// Use m_FromBPM & m_PercentChange to set new m_ToBPM & control.
{
   m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0));
   m_pTextCtrl_ToBPM->GetValidator()->TransferToWindow();
}

void EffectChangeTempo::Update_Text_ToLength()
// Use m_FromLength & m_PercentChange to set new m_ToLength & control.
{
   m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
   m_pTextCtrl_ToLength->GetValidator()->TransferToWindow();
}

#endif // USE_SOUNDTOUCH