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

  Audacity: A Digital Audio Editor

  DtmfGen.cpp

  Salvo Ventura - Dec 2006

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

\class EffectDtmf
\brief An effect that generates DTMF tones

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

#include "../Audacity.h"
#include "../Experimental.h"
#include "DtmfGen.h"

#include <wx/intl.h>
#include <wx/valgen.h>
#include <wx/valtext.h>

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


enum
{
   ID_Sequence,
   ID_Amplitude,
   ID_Duration,
   ID_DutyCycle,
};

// DA: DTMF for Audacity uses a different string.
#ifdef EXPERIMENTAL_DA
#define SHORT_APP_NAME "darkaudacity"
#else
#define SHORT_APP_NAME "audacity"
#endif

// Define keys, defaults, minimums, and maximums for the effect parameters
//
//     Name       Type        Key               Def                   Min      Max      Scale
Param( Sequence,  wxString,   wxT("Sequence"),   wxT(SHORT_APP_NAME),  wxT(""), wxT(""), wxT(""));
Param( DutyCycle, double,     wxT("Duty Cycle"), 55.0,                 0.0,     100.0,   10.0   );
Param( Amplitude, double,     wxT("Amplitude"),  0.8,                  0.001,   1.0,     1      );

static const double kFadeInOut = 250.0; // used for fadein/out needed to remove clicking noise

const static wxChar *kSymbols[] =
{
   wxT("0"), wxT("1"), wxT("2"), wxT("3"),
   wxT("4"), wxT("5"), wxT("6"), wxT("7"),
   wxT("8"), wxT("9"), wxT("*"), wxT("#"),
   wxT("A"), wxT("B"), wxT("C"), wxT("D"),
   wxT("a"), wxT("b"), wxT("c"), wxT("d"),
   wxT("e"), wxT("f"), wxT("g"), wxT("h"),
   wxT("i"), wxT("j"), wxT("k"), wxT("l"),
   wxT("m"), wxT("n"), wxT("o"), wxT("p"),
   wxT("q"), wxT("r"), wxT("s"), wxT("t"),
   wxT("u"), wxT("v"), wxT("w"), wxT("x"),
   wxT("y"), wxT("z")
};

//
// EffectDtmf
//

BEGIN_EVENT_TABLE(EffectDtmf, wxEvtHandler)
    EVT_TEXT(ID_Sequence, EffectDtmf::OnSequence)
    EVT_TEXT(ID_DutyCycle, EffectDtmf::OnAmplitude)
    EVT_TEXT(ID_Duration, EffectDtmf::OnDuration)
    EVT_SLIDER(ID_DutyCycle, EffectDtmf::OnDutyCycle)
END_EVENT_TABLE()

EffectDtmf::EffectDtmf()
{
   dtmfDutyCycle = DEF_DutyCycle;
   dtmfAmplitude = DEF_Amplitude;
   dtmfSequence = DEF_Sequence;
   dtmfTone = 0.0;
   dtmfSilence = 0.0;
}

EffectDtmf::~EffectDtmf()
{
}

// IdentInterface implementation

wxString EffectDtmf::GetSymbol()
{
   return DTMFTONES_PLUGIN_SYMBOL;
}

wxString EffectDtmf::GetDescription()
{
   return XO("Generates dual-tone multi-frequency (DTMF) tones like those produced by the keypad on telephones");
}

wxString EffectDtmf::ManualPage()
{
   return wxT("DTMF_Tones");
}

// EffectIdentInterface implementation

EffectType EffectDtmf::GetType()
{
   return EffectTypeGenerate;
}

// EffectClientInterface implementation

unsigned EffectDtmf::GetAudioOutCount()
{
   return 1;
}

bool EffectDtmf::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
   double duration = GetDuration();

   // all dtmf sequence durations in samples from seconds
   // MJS: Note that mDuration is in seconds but will have been quantised to the units of the TTC.
   // If this was 'samples' and the project rate was lower than the track rate,
   // extra samples may get created as mDuration may now be > mT1 - mT0;
   // However we are making our best efforts at creating what was asked for.

   auto nT0 = (sampleCount)floor(mT0 * mSampleRate + 0.5);
   auto nT1 = (sampleCount)floor((mT0 + duration) * mSampleRate + 0.5);
   numSamplesSequence = nT1 - nT0;  // needs to be exact number of samples selected

   //make under-estimates if anything, and then redistribute the few remaining samples
   numSamplesTone = sampleCount( floor(dtmfTone * mSampleRate) );
   numSamplesSilence = sampleCount( floor(dtmfSilence * mSampleRate) );

   // recalculate the sum, and spread the difference - due to approximations.
   // Since diff should be in the order of "some" samples, a division (resulting in zero)
   // is not sufficient, so we add the additional remaining samples in each tone/silence block,
   // at least until available.
   diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence;
   while (diff > 2*dtmfNTones - 1) {   // more than one per thingToBeGenerated
      // in this case, both numSamplesTone and numSamplesSilence would change, so it makes sense
      //  to recalculate diff here, otherwise just keep the value we already have

      // should always be the case that dtmfNTones>1, as if 0, we don't even start processing,
      // and with 1 there is no difference to spread (no silence slot)...
      wxASSERT(dtmfNTones > 1);
      numSamplesTone += (diff/(dtmfNTones));
      numSamplesSilence += (diff/(dtmfNTones-1));
      diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence;
   }
   wxASSERT(diff >= 0);  // should never be negative

   curSeqPos = -1; // pointer to string in dtmfSequence
   isTone = false;
   numRemaining = 0;

   return true;
}

size_t EffectDtmf::ProcessBlock(float **WXUNUSED(inbuf), float **outbuf, size_t size)
{
   float *buffer = outbuf[0];
   decltype(size) processed = 0;

   // for the whole dtmf sequence, we will be generating either tone or silence
   // according to a bool value, and this might be done in small chunks of size
   // 'block', as a single tone might sometimes be larger than the block
   // tone and silence generally have different duration, thus two generation blocks
   //
   // Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to
   // silence, I added a fade in/out of 1/250th of a second (4ms). This can still be
   // tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs.
   // A problem might be if the tone duration is very short (<10ms)... (?)
   //
   // One more problem is to deal with the approximations done when calculating the duration
   // of both tone and silence: in some cases the final sum might not be same as the initial
   // duration. So, to overcome this, we had a redistribution block up, and now we will spread
   // the remaining samples in every bin in order to achieve the full duration: test case was
   // to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation
   // you ended up with 3.999s or in other units: 3 seconds and 44097 samples.
   //
   while (size)
   {
      if (numRemaining == 0)
      {
         isTone = !isTone;

         if (isTone)
         {
            curSeqPos++;
            numRemaining = numSamplesTone;
            curTonePos = 0;
         }
         else
         {
            numRemaining = numSamplesSilence;
         }

         // the statement takes care of extracting one sample from the diff bin and
         // adding it into the current block until depletion
         numRemaining += (diff-- > 0 ? 1 : 0);         
      }

      const auto len = limitSampleBufferSize( size, numRemaining );

      if (isTone)
      {
         // generate the tone and append
         MakeDtmfTone(buffer, len, mSampleRate, dtmfSequence[curSeqPos], curTonePos, numSamplesTone, dtmfAmplitude);
         curTonePos += len;
      }
      else
      {
         memset(buffer, 0, sizeof(float) * len);
      }

      numRemaining -= len;

      buffer += len;
      size -= len;
      processed += len;
   }

   return processed;
}

bool EffectDtmf::GetAutomationParameters(EffectAutomationParameters & parms)
{
   parms.Write(KEY_Sequence, dtmfSequence);
   parms.Write(KEY_DutyCycle, dtmfDutyCycle);
   parms.Write(KEY_Amplitude, dtmfAmplitude);

   return true;
}

bool EffectDtmf::SetAutomationParameters(EffectAutomationParameters & parms)
{
   ReadAndVerifyDouble(DutyCycle);
   ReadAndVerifyDouble(Amplitude);
   ReadAndVerifyString(Sequence);

   wxString symbols;
   for (unsigned int i = 0; i < WXSIZEOF(kSymbols); i++)
   {
      symbols += kSymbols[i];
   }

   if (Sequence.find_first_not_of(symbols) != wxString::npos)
   {
      return false;
   }

   dtmfDutyCycle = DutyCycle;
   dtmfAmplitude = Amplitude;
   dtmfSequence = Sequence;
   
   Recalculate();

   return true;
}

// Effect implementation

bool EffectDtmf::Startup()
{
   wxString base = wxT("/Effects/DtmfGen/");

   // Migrate settings from 2.1.0 or before

   // Already migrated, so bail
   if (gPrefs->Exists(base + wxT("Migrated")))
   {
      return true;
   }

   // Load the old "current" settings
   if (gPrefs->Exists(base))
   {
      gPrefs->Read(base + wxT("String"), &dtmfSequence, wxT(SHORT_APP_NAME));
      gPrefs->Read(base + wxT("DutyCycle"), &dtmfDutyCycle, 550L);
      gPrefs->Read(base + wxT("Amplitude"), &dtmfAmplitude, 0.8f);

      SaveUserPreset(GetCurrentSettingsGroup());

      // Do not migrate again
      gPrefs->Write(base + wxT("Migrated"), true);
      gPrefs->Flush();
   }

   return true;
}

bool EffectDtmf::Init()
{
   Recalculate();

   return true;
}

void EffectDtmf::PopulateOrExchange(ShuttleGui & S)
{
   // dialog will be passed values from effect
   // Effect retrieves values from saved config
   // Dialog will take care of using them to initialize controls
   // If there is a selection, use that duration, otherwise use
   // value from saved config: this is useful is user wants to
   // replace selection with dtmf sequence

   S.AddSpace(0, 5);
   S.StartMultiColumn(2, wxCENTER);
   {
      wxTextValidator vldDtmf(wxFILTER_INCLUDE_CHAR_LIST, &dtmfSequence);
      vldDtmf.SetIncludes(wxArrayString(WXSIZEOF(kSymbols), kSymbols));
      mDtmfSequenceT = S.Id(ID_Sequence).AddTextBox(_("DTMF sequence:"), wxT(""), 10);
      mDtmfSequenceT->SetValidator(vldDtmf);

      FloatingPointValidator<double> vldAmp(3, &dtmfAmplitude, NUM_VAL_NO_TRAILING_ZEROES);
      vldAmp.SetRange(MIN_Amplitude, MAX_Amplitude);
      S.Id(ID_Amplitude).AddTextBox(_("Amplitude (0-1):"), wxT(""), 10)->SetValidator(vldAmp);

      S.AddPrompt(_("Duration:"));
      mDtmfDurationT = safenew
         NumericTextCtrl(NumericConverter::TIME,
                         S.GetParent(),
                         ID_Duration,
                         GetDurationFormat(),
                         GetDuration(),
                         mProjectRate,
                         wxDefaultPosition,
                         wxDefaultSize,
                         true);
      mDtmfDurationT->SetName(_("Duration"));
      mDtmfDurationT->EnableMenu();
      S.AddWindow(mDtmfDurationT);

      S.AddFixedText(_("Tone/silence ratio:"), false);
      S.SetStyle(wxSL_HORIZONTAL | wxEXPAND);
      mDtmfDutyCycleS = S.Id(ID_DutyCycle).AddSlider(wxT(""),
                                                     dtmfDutyCycle * SCL_DutyCycle,
                                                     MAX_DutyCycle * SCL_DutyCycle, 
                                                     MIN_DutyCycle * SCL_DutyCycle);
      S.SetSizeHints(-1,-1);
   }
   S.EndMultiColumn();

   S.StartMultiColumn(2, wxCENTER);
   {
      S.AddFixedText(_("Duty cycle:"), false);
      mDtmfDutyT = S.AddVariableText(wxString::Format(wxT("%.1f %%"), dtmfDutyCycle), false);
      
      S.AddFixedText(_("Tone duration:"), false);
      mDtmfSilenceT = S.AddVariableText(wxString::Format(wxString(wxT("%.0f ")) + _("ms"), dtmfTone * 1000.0), false);

      S.AddFixedText(_("Silence duration:"), false);
      mDtmfToneT = S.AddVariableText(wxString::Format(wxString(wxT("%0.f ")) + _("ms"), dtmfSilence * 1000.0), false);
   }
   S.EndMultiColumn();
}

bool EffectDtmf::TransferDataToWindow()
{
   Recalculate();

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

   mDtmfDutyCycleS->SetValue(dtmfDutyCycle * SCL_DutyCycle);

   mDtmfDurationT->SetValue(GetDuration());

   UpdateUI();

   return true;
}

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

   dtmfDutyCycle = (double) mDtmfDutyCycleS->GetValue() / SCL_DutyCycle;
   SetDuration(mDtmfDurationT->GetValue());

   // recalculate to make sure all values are up-to-date. This is especially
   // important if the user did not change any values in the dialog
   Recalculate();

   return true;
}

// EffectDtmf implementation

void EffectDtmf::Recalculate()
{
   // remember that dtmfDutyCycle is in range (0.0-100.0)

   dtmfNTones = (int) dtmfSequence.Length();

   if (dtmfNTones==0) {
      // no tones, all zero: don't do anything
      // this should take care of the case where user got an empty
      // dtmf sequence into the generator: track won't be generated
      SetDuration(0.0);
      dtmfTone = 0;
      dtmfSilence = 0;
   } else {
      if (dtmfNTones==1) {
        // single tone, as long as the sequence
          dtmfTone = GetDuration();
          dtmfSilence = 0;
      } else {
         // Don't be fooled by the fact that you divide the sequence into dtmfNTones:
         // the last slot will only contain a tone, not ending with silence.
         // Given this, the right thing to do is to divide the sequence duration
         // by dtmfNTones tones and (dtmfNTones-1) silences each sized according to the duty
         // cycle: original division was:
         // slot=mDuration / (dtmfNTones*(dtmfDutyCycle/MAX_DutyCycle)+(dtmfNTones-1)*(1.0-dtmfDutyCycle/MAX_DutyCycle))
         // which can be simplified in the one below.
         // Then just take the part that belongs to tone or silence.
         //
         double slot = GetDuration() / ((double)dtmfNTones + (dtmfDutyCycle / 100.0) - 1);
         dtmfTone = slot * (dtmfDutyCycle / 100.0); // seconds
         dtmfSilence = slot * (1.0 - (dtmfDutyCycle / 100.0)); // seconds

         // Note that in the extremes we have:
         // - dutyCycle=100%, this means no silence, so each tone will measure mDuration/dtmfNTones
         // - dutyCycle=0%, this means no tones, so each silence slot will measure mDuration/(NTones-1)
         // But we always count:
         // - dtmfNTones tones
         // - dtmfNTones-1 silences
      }
   }
}

bool EffectDtmf::MakeDtmfTone(float *buffer, size_t len, float fs, wxChar tone, sampleCount last, sampleCount total, float amplitude)
{
/*
  --------------------------------------------
              1209 Hz 1336 Hz 1477 Hz 1633 Hz

                          ABC     DEF
   697 Hz          1       2       3       A

                  GHI     JKL     MNO
   770 Hz          4       5       6       B

                  PQRS     TUV     WXYZ
   852 Hz          7       8       9       C

                          oper
   941 Hz          *       0       #       D
  --------------------------------------------
  Essentially we need to generate two sin with
  frequencies according to this table, and sum
  them up.
  sin wave is generated by:
   s(n)=sin(2*pi*n*f/fs)

  We will precalculate:
     A= 2*pi*f1/fs
     B= 2*pi*f2/fs

  And use two switch statements to select the frequency

  Note: added support for letters, like those on the keypad
        This support is only for lowercase letters: uppercase
        are still considered to be the 'military'/carrier extra
        tones.
*/

   float f1, f2=0.0;
   double A,B;

   // select low tone: left column
   switch (tone) {
      case '1':   case '2':   case '3':   case 'A':
      case 'a':   case 'b':   case 'c':
      case 'd':   case 'e':   case 'f':
         f1=697;
         break;
      case '4':   case '5':   case '6':   case 'B':
      case 'g':   case 'h':   case 'i':
      case 'j':   case 'k':   case 'l':
      case 'm':   case 'n':   case 'o':
         f1=770;
         break;
      case '7':   case '8':   case '9':   case 'C':
      case 'p':   case 'q':   case 'r':   case 's':
      case 't':   case 'u':   case 'v':
      case 'w':   case 'x':   case 'y':   case 'z':
         f1=852;
         break;
      case '*':   case '0':   case '#':   case 'D':
         f1=941;
         break;
      default:
         f1=0;
   }

   // select high tone: top row
   switch (tone) {
      case '1':   case '4':   case '7':   case '*':
      case 'g':   case 'h':   case 'i':
      case 'p':   case 'q':   case 'r':   case 's':
         f2=1209;
         break;
      case '2':   case '5':   case '8':   case '0':
      case 'a':   case 'b':   case 'c':
      case 'j':   case 'k':   case 'l':
      case 't':   case 'u':   case 'v':
         f2=1336;
         break;
      case '3':   case '6':   case '9':   case '#':
      case 'd':   case 'e':   case 'f':
      case 'm':   case 'n':   case 'o':
      case 'w':   case 'x':   case 'y':   case 'z':
         f2=1477;
         break;
      case 'A':   case 'B':   case 'C':   case 'D':
         f2=1633;
         break;
      default:
         f2=0;
   }

   // precalculations
   A=B=2*M_PI/fs;
   A*=f1;
   B*=f2;

   // now generate the wave: 'last' is used to avoid phase errors
   // when inside the inner for loop of the Process() function.
   for(decltype(len) i = 0; i < len; i++) {
      buffer[i] = amplitude * 0.5 *
         (sin( A * (i + last).as_double() ) +
          sin( B * (i + last).as_double() ));
   }

   // generate a fade-in of duration 1/250th of second
   if (last == 0) {
      A = (fs / kFadeInOut);
      for(size_t i = 0; i < A; i++) {
         buffer[i] *= i/A;
      }
   }

   // generate a fade-out of duration 1/250th of second
   if (last == total - len) {
      // we are at the last buffer of 'len' size, so, offset is to
      // backup 'A' samples, from 'len'
      A = (fs / kFadeInOut);
      auto offset = long(len) - long(fs / kFadeInOut);
      // protect against negative offset, which can occur if too a
      // small selection is made
      if (offset >= 0) {
         for(size_t i = 0; i < A; i++) {
            buffer[i + offset] *= (1 - (i / A));
         }
      }
   }
   return true;
}

void EffectDtmf::UpdateUI(void)
{
   mDtmfDutyT->SetLabel(wxString::Format(wxT("%.1f %%"), dtmfDutyCycle));
   mDtmfDutyT->SetName(mDtmfDutyT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)

   mDtmfSilenceT->SetLabel(wxString::Format(wxString(wxT("%.0f ")) + _("ms"), dtmfTone * 1000.0));
   mDtmfSilenceT->SetName(mDtmfSilenceT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)

   mDtmfToneT->SetLabel(wxString::Format(wxString(wxT("%0.f ")) + _("ms"), dtmfSilence * 1000.0));
   mDtmfToneT->SetName(mDtmfToneT->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
}

void EffectDtmf::OnSequence(wxCommandEvent & WXUNUSED(evt))
{
   dtmfSequence = mDtmfSequenceT->GetValue();
   Recalculate();
   UpdateUI();
}

void EffectDtmf::OnAmplitude(wxCommandEvent & WXUNUSED(evt))
{
   if (!mDtmfAmplitudeT->GetValidator()->TransferFromWindow())
   {
      return;
   }
   Recalculate();
   UpdateUI();
}
void EffectDtmf::OnDuration(wxCommandEvent & WXUNUSED(evt))
{
   SetDuration(mDtmfDurationT->GetValue());
   Recalculate();
   UpdateUI();
}

void EffectDtmf::OnDutyCycle(wxCommandEvent & evt)
{
   dtmfDutyCycle = (double) evt.GetInt() / SCL_DutyCycle;
   Recalculate();
   UpdateUI();
}
