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

  Audacity: A Digital Audio Editor

  TruncSilence.cpp

  Lynn Allan (from DM's Normalize)
  ToDo: Only works on complete mono track for now
  ToDo: mBlendFrameCount only retrieved from prefs ... not using dialog
        Only way to change (for windows) is thru registry
        The values should be figured dynamically ... too many frames could be invalid

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

#include <wx/wx.h>
#include <math.h>

#include "../Prefs.h"
#include "../Project.h"
#include "TruncSilence.h"

EffectTruncSilence::EffectTruncSilence() : mUserPrompted(false)
{
   Init();
}

bool EffectTruncSilence::Init()
{
   mTruncLongestAllowedSilentMs = gPrefs->Read(wxT("/CsPresets/TruncLongestAllowedSilentMs"), 200L);
   if ((mTruncLongestAllowedSilentMs < 0) || (mTruncLongestAllowedSilentMs >= 9999999)) {  // corrupted Prefs?
      mTruncLongestAllowedSilentMs = SKIP_EFFECT_MILLISECOND;
      gPrefs->Write(wxT("/CsPresets/TruncLongestAllowedSilentMs"), (int)mTruncLongestAllowedSilentMs);
   }
   mTruncDbChoiceIndex = gPrefs->Read(wxT("/CsPresets/TruncDbChoiceIndex"), 4L);
   if ((mTruncDbChoiceIndex < 0) || (mTruncDbChoiceIndex >= Enums::NumDbChoices)) {  // corrupted Prefs?
      mTruncDbChoiceIndex = Enums::NumDbChoices - 1;  // Off-Skip
      gPrefs->Write(wxT("/CsPresets/TruncDbChoiceIndex"), mTruncDbChoiceIndex);
      mTruncLongestAllowedSilentMs = SKIP_EFFECT_MILLISECOND;
      gPrefs->Write(wxT("/CsPresets/TruncLongestAllowedSilentMs"), (int)mTruncLongestAllowedSilentMs);
   }
   mBlendFrameCount = gPrefs->Read(wxT("/CsPresets/BlendFrameCount"), 100L);
   if ((mBlendFrameCount < 0) || (mBlendFrameCount >= 5000)) {  // corrupted Prefs?
      mBlendFrameCount = 100;
      gPrefs->Write(wxT("/CsPresets/BlendFrameCount"), 100);
   }
   TrackListIterator iter(mWaveTracks);
   mTrack = (WaveTrack *) iter.First();
   if (mTrack != 0) {
      int numTracks = GetNumWaveTracks();
      if (numTracks != 1) {
          wxMessageBox(_("Sorry. Truncate Silence only valid with mono track"));
          return false;
      }
   }
   return true;
}

bool EffectTruncSilence::CheckWhetherSkipEffect()
{
   bool rc = ((mTruncDbChoiceIndex >= (Enums::NumDbChoices - 1))
          ||  (mTruncLongestAllowedSilentMs >= SKIP_EFFECT_MILLISECOND));
   return rc;
}

void EffectTruncSilence::End()
{
}

bool EffectTruncSilence::PromptUser()
{
   TruncSilenceDialog dlog(mParent, -1, _("Truncate Silence"));
   dlog.mTruncLongestAllowedSilentMs = mTruncLongestAllowedSilentMs;
   dlog.mTruncDbChoiceIndex = mTruncDbChoiceIndex;
   dlog.TransferDataToWindow();

   dlog.CentreOnParent();
   dlog.ShowModal();

   if (!dlog.GetReturnCode()) {
      return false;
   }
   mUserPrompted = true;
   mTruncLongestAllowedSilentMs = dlog.mTruncLongestAllowedSilentMs;
   gPrefs->Write(wxT("/CsPresets/TruncLongestAllowedSilentMs"), (long)mTruncLongestAllowedSilentMs);

   mTruncDbChoiceIndex = dlog.mTruncDbChoiceIndex;
   gPrefs->Write(wxT("/CsPresets/TruncDbChoiceIndex"), mTruncDbChoiceIndex);
   
   return true;
}

bool EffectTruncSilence::TransferParameters( Shuttle & shuttle )
{  
   shuttle.TransferEnum(wxT("Db"),mTruncDbChoiceIndex,Enums::NumDbChoices,Enums::GetDbChoices());
   shuttle.TransferLongLong(wxT("Duration"),mTruncLongestAllowedSilentMs,200);
   return true;
}

bool EffectTruncSilence::Process()
{
   ProcessOne();

   mUserPrompted = false;
   return true;
}

#define QUARTER_SECOND_MS 250
bool EffectTruncSilence::ProcessOne()
{
   float  curFrame;
   float  fabsCurFrame;
   int    consecutiveSilentFrames = 0;
   int    samplesCleared = 0;
   int    truncIndex = 0;
   double curRate = mTrack->GetRate();
   int    quarterSecondFrames = (int)((curRate * QUARTER_SECOND_MS)/ 1000.0);
   int    truncLongestAllowedSilentSamples = int((mTruncLongestAllowedSilentMs * curRate) / 1000.0);
   double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex];
   bool   ignoringFrames = false;

   double selectionEnd = mT1;
   double trackEnd = mTrack->GetEndTime();
   longSampleCount selectionStartLongSampleCount = mTrack->TimeToLongSamples(mT0);
   longSampleCount selectionEndLongSampleCount = mTrack->TimeToLongSamples(mT1);
   sampleCount selectionStartSampleCount = selectionStartLongSampleCount;
   sampleCount selectionEndSampleCount = selectionEndLongSampleCount;

   sampleCount idealBlockLen = mTrack->GetMaxBlockSize() * 8;   // bigger buffer reduces 'reset'
   sampleCount index = selectionStartSampleCount;
   sampleCount indexAtLoopStart = index;
   sampleCount outTrackOffset = selectionStartSampleCount;
   float *buffer = new float[idealBlockLen];
   bool rc;

   int rampInFrames = (truncLongestAllowedSilentSamples / 4);
   if (rampInFrames > quarterSecondFrames) {
      rampInFrames = quarterSecondFrames;
   }
   while (index < selectionEndSampleCount) {
      rc = mTrack->Get((samplePtr)buffer, floatSample, index, idealBlockLen);
      truncIndex = 0;
      indexAtLoopStart = index;
      ignoringFrames = false;       // may be unneccesary to reset, but safer
      consecutiveSilentFrames = 0;  // may be unneccesary to reset, but safer
      sampleCount limit = idealBlockLen;
      if ((index + idealBlockLen) > selectionEndSampleCount) {
         limit = selectionEndSampleCount - index; 
      }
      for (sampleCount i = 0; i < limit; ++i) {
         index++;
         curFrame = buffer[i];
         fabsCurFrame = (float)fabs(curFrame);

         if (fabsCurFrame < truncDbSilenceThreshold) {
            consecutiveSilentFrames++;
            if (consecutiveSilentFrames > truncLongestAllowedSilentSamples) {
               ignoringFrames = true;
               continue;  // ignore this frame (equivalent to cutting it)
            }             // otherwise, keep sample to be part of allowed silence
         }
         else {
            if (ignoringFrames == true) {
               sampleCount curOffset = i - rampInFrames;
               truncIndex -= rampInFrames; // backup into ignored frames
               wxASSERT((truncIndex < idealBlockLen) && (curOffset > 0) && ((curOffset + rampInFrames) < idealBlockLen));
               for (int fr = 0; fr < rampInFrames; ++fr) {
                  buffer[truncIndex] = buffer[curOffset + fr];
                  truncIndex++;
               }
               BlendFrames(buffer, mBlendFrameCount,
                          ((truncIndex - rampInFrames) - mBlendFrameCount), 
                          ((i - rampInFrames) - mBlendFrameCount));
            }
            consecutiveSilentFrames = 0;
            ignoringFrames = false;
         }
         wxASSERT(truncIndex < idealBlockLen);
         buffer[truncIndex] = curFrame;  // Can get here either because > dbThreshold
         truncIndex++;                   // or silence duration isn't longer than allowed
      }
      samplesCleared += (limit - truncIndex);
      rc = mTrack->Set((samplePtr)buffer, floatSample, outTrackOffset, truncIndex);
      outTrackOffset += truncIndex;
      bool cancellingFlag = TrackProgress(0, ((double)index / (double)selectionEndSampleCount));
      if (cancellingFlag == true) {
         delete [] buffer;
         return false;
      }
   }
   double lenToCut = (samplesCleared / curRate);
   double middle =  selectionEnd - lenToCut;
   mTrack->Cut(middle, selectionEnd, (Track**)&mTrack);

   mT0 = 0.0;
   mT1 = trackEnd - lenToCut;
   if (mUserPrompted == true) {
      AudacityProject *proj = GetActiveProject();
      proj->OnZoomFit();
   }
   gPrefs->Write(wxT("/Validate/TruncSamplesCleared"), samplesCleared);
   delete [] buffer;
   return true;
}

void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int leftIndex, int rightIndex)
{
   float* bufOutput = &buffer[leftIndex];
   float* bufBefore = &buffer[leftIndex];
   float* bufAfter  = &buffer[rightIndex];
   double beforeFactor = 1.0;
   double afterFactor  = 0.0;
   double adjFactor = 1.0 / (double)blendFrameCount;
   for (int j = 0; j < blendFrameCount; ++j) {
	  bufOutput[j] = (float)((bufBefore[j] * beforeFactor) + (bufAfter[j] * afterFactor));
	  beforeFactor -= adjFactor;
	  afterFactor  += adjFactor;
   }
}

//----------------------------------------------------------------------------
// TruncSilenceDialog
//----------------------------------------------------------------------------

#define ID_LONGEST_SILENCE_TEXT   7000
#define ID_DB_SILENCE_THRESHOLD_CHOICE 7001

BEGIN_EVENT_TABLE(TruncSilenceDialog,wxDialog)
   EVT_BUTTON( wxID_OK, TruncSilenceDialog::OnOk )
   EVT_BUTTON( wxID_CANCEL, TruncSilenceDialog::OnCancel )
END_EVENT_TABLE()

TruncSilenceDialog::TruncSilenceDialog(wxWindow *parent, wxWindowID id, const wxString &title) :
   wxDialog( parent, id, title )
{
   wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
   wxStaticText *statText = new wxStaticText(this, -1,
                           _("Truncate Silence by Lynn Allan"));
   mainSizer->Add(statText, 0, wxALIGN_CENTRE | wxALL, 5);

   wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL);

   statText = new wxStaticText(this, -1, _("Max Silent Duration (milliseconds): \n(99999 or greater is off)"));
   hSizer->Add(statText, 0, wxALIGN_CENTRE | wxALL, 5);
   
   wxString truncLongestAllowedSilentMsStr;
   truncLongestAllowedSilentMsStr.Printf(wxT("%d"), mTruncLongestAllowedSilentMs);
   mTruncLongestAllowedSilentMsText = new wxTextCtrl(this, ID_LONGEST_SILENCE_TEXT, 
                     truncLongestAllowedSilentMsStr, wxDefaultPosition,
                     wxSize(60, -1), 0,
                     wxTextValidator(wxFILTER_NUMERIC));
   hSizer->Add(mTruncLongestAllowedSilentMsText, 0, wxALL, 5);
   mainSizer->Add(hSizer, 0, wxALIGN_CENTRE | wxALL, 5);
   hSizer = new wxBoxSizer(wxHORIZONTAL);

   statText = new wxStaticText(this, -1, _("Theshold for silence: "));
   hSizer->Add(statText, 0, wxALIGN_CENTRE | wxALL, 5);

   mTruncDbSilenceThresholdChoice = new wxChoice(this, ID_DB_SILENCE_THRESHOLD_CHOICE,
      wxDefaultPosition, wxSize(64, -1), Enums::NumDbChoices,Enums::GetDbChoices());
   hSizer->Add(mTruncDbSilenceThresholdChoice, 0, wxALIGN_CENTER | wxALL, 4);
   mainSizer->Add(hSizer, 0, wxALIGN_CENTRE | wxALL, 5);
   hSizer = new wxBoxSizer(wxHORIZONTAL);

   wxButton *cancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
   hSizer->Add(cancel, 0, wxALIGN_CENTRE|wxALL, 5);

   wxButton *ok = new wxButton(this, wxID_OK, _("OK"));
   ok->SetDefault();
   ok->SetFocus();
   hSizer->Add(ok, 0, wxALIGN_CENTRE|wxALL, 5);

   mainSizer->Add(hSizer, 0, wxALIGN_CENTRE|wxALIGN_CENTER_VERTICAL|wxALL, 5);

   SetAutoLayout(true);
   SetSizer(mainSizer);
   mainSizer->Fit(this);
   mainSizer->SetSizeHints(this);
}

bool TruncSilenceDialog::TransferDataToWindow()
{
   mTruncLongestAllowedSilentMsText->SetValue(wxString::Format(wxT("%d"), mTruncLongestAllowedSilentMs));
   mTruncDbSilenceThresholdChoice->SetSelection(mTruncDbChoiceIndex);

   return true;
}

bool TruncSilenceDialog::TransferDataFromWindow()
{
   long ms;
   mTruncLongestAllowedSilentMsText->GetValue().ToLong(&ms);
   mTruncLongestAllowedSilentMs = ms;

   mTruncDbChoiceIndex = mTruncDbSilenceThresholdChoice->GetSelection();

   return true;
}

void TruncSilenceDialog::OnOk(wxCommandEvent &event)
{
   TransferDataFromWindow();

   EndModal(true);
}

void TruncSilenceDialog::OnCancel(wxCommandEvent &event)
{
   EndModal(false);
}
