Mark Silence effect as C++ code

This read-only archive contains discussions from the Adding Feature forum.
New feature request may be posted to the Adding Feature forum.
Technical support is available via the Help forum.
Edgar
Forum Crew
Posts: 2043
Joined: Thu Sep 03, 2009 9:13 pm
Operating System: Windows 10

Mark Silence effect as C++ code

Post by Edgar » Sun Dec 06, 2015 10:54 pm

I got this working with two caveats - no progress dialog (wastes too much time and resources) and the "Minimum amplitude (-dB) to define as silence" does not work as negative dBs. I need to figure out how to convert negative decibels (e.g. -26 for vinyl or -32 for CDs) into the (abs() of) sample float values. In the Nyquist versions we have a value entered in negative decibels but the C++ code looks at sample values which run from -1.0 to +1.0 and a reasonable (upon brief experimentation) "silence" value for CD recordings seems to be between -.01 and +.01 (or less than abs(-.01) or +.01); for vinyl -.02 -> +.02).

My code if fairly quick and on my machine (with 1.6 gigabytes of available RAM) I can process 3 hours and 25 minutes of 44100 samples/second audio. It is also way better than the Nyquist version(s) at dropping the label exactly in the center of the "silence" and ignoring false positives.

[edit 15 December 2015]
In the original post I forgot to mention adding the effect(s) to the Effect Manager; this will need to be done in LoadEffects.cpp; in the old 2.0 style Effects Manager code, for the new Mark Silence effect around line 270 (in the Analyze menu):

Code: Select all

   em.RegisterEffect(new EffectReverse());
   em.RegisterEffect(new EffectStereoToMono(), HIDDEN_EFFECT);// NOT in normal effects list.
   em.RegisterEffect(new EffectTrimFrontBack(), SIMPLE_EFFECT);
   em.RegisterEffect(new EffectTruncSilence(), SIMPLE_EFFECT);
and for the new Trim Front/Back effect around line 260 (in the Effect menu):

Code: Select all

   // Analyze menu
   em.RegisterEffect(new EffectFindClipping());
   em.RegisterEffect(new EffectMarkSilence());
I'm not quite sure how to do this yet in the new 2.1 Effect Manager code!
[end edit]

For production code I would create a new header and source code file but for quick-and-dirty I just add my new header info to the end of FindClipping.h:

Code: Select all

/////////////////////////////////////////////////////////////////////
//Mark Silence Effect

#ifndef __AUDACITY_EFFECT_FINDCLIPPING__
#define __AUDACITY_EFFECT_FINDCLIPPING__

class wxString;

#include <wx/dialog.h>

#include <wx/intl.h>

#include "Effect.h"

class wxStaticText;

class WaveTrack;

class EffectMarkSilence:public Effect
{
   friend class MarkSilenceDialog;

public:

   EffectMarkSilence();

   virtual wxString GetEffectName()
   {
      return wxString(_("Mark Silence..."));
   }

   virtual std::set<wxString> GetEffectCategories()
   {
      std::set<wxString> result;
      result.insert(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin"));
      return result;
   }

   virtual wxString GetEffectIdentifier()
   {
      return wxString(wxT("MarkSilence"));
   }

   virtual wxString GetEffectAction()
   {
      return wxString(_("Adding label(s) to mark silence(s)"));
   }

   virtual wxString GetEffectDescription();

   virtual bool PromptUser();
   virtual bool TransferParameters(Shuttle & shuttle);

   virtual bool Process();

private:
   bool ProcessOne(LabelTrack *l, int count, WaveTrack * t,
      sampleCount start, sampleCount len);

   bool bAddLeadingLabel;
   double dMinimumAmplitude;//Minimum amplitude (-dB) to define as silence
   double dMinimumDuration;//Minimum duration (seconds) to define as silent gap
   double dMinimumWait;//Minimum Wait time (seconds) Before looking for another silent gap
};

//----------------------------------------------------------------------------
// MarkSilenceDialog
//----------------------------------------------------------------------------
class MarkSilenceDialog:public EffectDialog {
public:
   MarkSilenceDialog(EffectMarkSilence * effect, wxWindow * parent);

   void PopulateOrExchange(ShuttleGui & S);
   bool TransferDataFromWindow();

private:
   EffectMarkSilence *mEffect;
};

#endif // __AUDACITY_EFFECT_FINDCLIPPING__
and the new source code to FindClipping.cpp:

Code: Select all

/////////////////////////////////////////////////////////////////////
//Mark Silence Effect

EffectMarkSilence::EffectMarkSilence()
{
   SetEffectFlags(BUILTIN_EFFECT | ANALYZE_EFFECT);
   bAddLeadingLabel = false;
   dMinimumAmplitude = -0.01;
   dMinimumDuration = 2.5;
   dMinimumWait = 10.0;
   //dMinimumAmplitude = -28.0;
   //dMinimumWait = 20.0;
}

wxString EffectMarkSilence::GetEffectDescription()
{
   return wxString::Format(_("Adding label(s) to mark silence(s)"));
}

bool EffectMarkSilence::PromptUser()
{
   MarkSilenceDialog dlg(this, mParent);
   dlg.CentreOnParent();

   if (dlg.ShowModal() == wxID_CANCEL) {
      return false;
   }

   return true;
}

bool EffectMarkSilence::TransferParameters(Shuttle & shuttle)
{
   shuttle.TransferBool(wxT("Leading"), bAddLeadingLabel, false);
   shuttle.TransferDouble(wxT("Amplitude"), dMinimumAmplitude, -28.0);
   shuttle.TransferDouble(wxT("Duration"), dMinimumDuration, 2.5);
   shuttle.TransferDouble(wxT("Wait"), dMinimumWait, 20.0);

   return true;
}

bool EffectMarkSilence::Process()
{
   LabelTrack * labelTrack = NULL;
   Track * original = NULL;

   TrackListOfKindIterator iter(Track::Label, mTracks);
   for (Track * track = iter.First(); track; track = iter.Next()) {
      if (track->GetName() == wxT("Silences")) {
         labelTrack = (LabelTrack *)track;
         // copy LabelTrack here, so it can be undone on cancel
         labelTrack->Copy(labelTrack->GetStartTime(), labelTrack->GetEndTime(), &original);
         original->SetOffset(labelTrack->GetStartTime());
         original->SetName(wxT("Silences"));
         break;
      }
   }

   if (!labelTrack) {
      labelTrack = mFactory->NewLabelTrack();
      labelTrack->SetName(_("Silences"));
      mTracks->Add((Track *)labelTrack);
   }

   int count = 0;

   // JC: Only process selected tracks.
   SelectedTrackListOfKindIterator waves(Track::Wave, mTracks);
   WaveTrack * waveTrack = (WaveTrack *)waves.First();
   while (waveTrack) {
      double trackStart = waveTrack->GetStartTime();
      double trackEnd = waveTrack->GetEndTime();
      double t0 = mT0 < trackStart ? trackStart : mT0;
      double t1 = mT1 > trackEnd ? trackEnd : mT1;

      if (t1 > t0) {
         sampleCount start = waveTrack->TimeToLongSamples(t0);
         sampleCount end = waveTrack->TimeToLongSamples(t1);
         sampleCount len = (sampleCount)(end - start);

         if (!ProcessOne(labelTrack, count, waveTrack, start, len)) {
            //put it back how it was
            mTracks->Remove((Track *)labelTrack);
            if (original) {
               mTracks->Add((Track *)original);
            }
            return false;
         }
      }

      count++;
      waveTrack = (WaveTrack *)waves.Next();
   }

   return true;
}

bool EffectMarkSilence::ProcessOne(LabelTrack * pLabelTrack, int pCount, WaveTrack * pWaveTrack, 
   sampleCount pStart, sampleCount pLength)
{
   bool bGoodResult = true;
   float * buffer;
   try {
      buffer = new float[pLength];
   }
   catch (std::bad_alloc&) {
#ifdef __WXMSW__
      MEMORYSTATUS   memInfo;
      memInfo.dwLength = sizeof(memInfo);
      GlobalMemoryStatus(&memInfo);
      unsigned long totalRAM = memInfo.dwTotalPhys, availableRAM = memInfo.dwAvailPhys;
#endif //__WXWSW__
      wxMessageBox(_("Insufficient RAM to process sample"));
      return true;
   }
   double positiveAmplitude = dMinimumAmplitude * -1.0, trackRate = pWaveTrack->GetRate();
   sampleCount duration = dMinimumDuration * trackRate, waittime = dMinimumWait * trackRate;
   sampleCount lastUseful = pLength - duration;
   if (bAddLeadingLabel)
      pLabelTrack->AddLabel(0.0, 0.0);
   if (duration >= pLength)
      return true;

   bGoodResult = pWaveTrack->Get((samplePtr)buffer, floatSample, pStart, pLength);

   for (sampleCount bufferLocation = (duration / 2); bufferLocation < pLength; bufferLocation++) {
      if (fabs(buffer[bufferLocation]) <= positiveAmplitude) {
         for (sampleCount endLocation = bufferLocation + 1; endLocation < lastUseful; endLocation++) {
            if (fabs(buffer[endLocation]) >= positiveAmplitude) {
               if ((endLocation - bufferLocation) >= duration) {
                  double centerSilence = (bufferLocation + ((endLocation - bufferLocation) / 2)) / trackRate;
                  pLabelTrack->AddLabel(centerSilence, centerSilence);
                  bufferLocation = endLocation + waittime;
               }
               else
                  bufferLocation = endLocation + 1;
               endLocation = lastUseful;
            }
         }
      }
   }

   delete[] buffer;

   return bGoodResult;
}

//----------------------------------------------------------------------------
// MarkSilenceDialog
//----------------------------------------------------------------------------

MarkSilenceDialog::MarkSilenceDialog(EffectMarkSilence * effect, wxWindow * parent)
   : EffectDialog(parent, _("Mark Silence"), INSERT_EFFECT)
{  
   mEffect = effect;

   Init();
}

void MarkSilenceDialog::PopulateOrExchange(ShuttleGui & S)
{
   S.StartHorizontalLay(wxEXPAND, 0);
   {
      S.SetBorder(2);
      S.StartStatic(_("Extra Labels"), 1);
      {
         S.TieCheckBox(_("Place label at the beginning of the track"), mEffect->bAddLeadingLabel);
      }
      S.EndStatic();
   }
   S.EndHorizontalLay();

   S.SetBorder(2);
   S.StartStatic(_("Variables"), 1);
   {
      S.StartMultiColumn(2, wxALIGN_LEFT);
      {
         S.TieTextBox(_("Minimum amplitude to define as silence (-dB):"),
            mEffect->dMinimumAmplitude,
            20)->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

         S.TieTextBox(_("Minimum duration to define as silent gap (seconds):"),
            mEffect->dMinimumDuration,
            20)->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

         S.TieTextBox(_("Minimum wait time before looking for another silent gap (seconds):"),
            mEffect->dMinimumWait,
            20)->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
      }
      S.EndMultiColumn();
   }
   S.EndStatic();
}

bool MarkSilenceDialog::TransferDataFromWindow()
{
   EffectDialog::TransferDataFromWindow();
   return true;
}
There is some memory snooping code still in there:

Code: Select all

#ifdef __WXMSW__
      MEMORYSTATUS   memInfo;
which is only defined for Windows.
Last edited by Edgar on Tue Dec 15, 2015 8:47 pm, edited 2 times in total.
-Edgar
running Audacity personally customized 2.0.6 daily in a professional audio studio
occasionally using current Audacity alpha for testing and support situations
64-bit Windows Pro 10

Edgar
Forum Crew
Posts: 2043
Joined: Thu Sep 03, 2009 9:13 pm
Operating System: Windows 10

Re: Mark Silence effect as C++ code

Post by Edgar » Mon Dec 07, 2015 1:28 am

Oops! All of my design and debugging was done using a mono track. Processing a stereo track results in a duplicate set of labels. I now know how to fix this but it will be a day or two before I have the code for stereo tracks working.
-Edgar
running Audacity personally customized 2.0.6 daily in a professional audio studio
occasionally using current Audacity alpha for testing and support situations
64-bit Windows Pro 10

steve
Site Admin
Posts: 81609
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Mark Silence effect as C++ code

Post by steve » Mon Dec 07, 2015 3:54 am

Edgar wrote:It is also way better than the Nyquist version(s) at dropping the label exactly in the center of the "silence"
It is by design that the Nyquist version places the label at a prescribed period before the end of the silence (not in the center of the silence).
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

Edgar
Forum Crew
Posts: 2043
Joined: Thu Sep 03, 2009 9:13 pm
Operating System: Windows 10

Re: Mark Silence effect as C++ code

Post by Edgar » Fri Dec 11, 2015 11:18 pm

I think this is ready for serious testing. I have also included another effect "Trim Silence" which trims silence from the front, back or both ends of an audio track. These images are from Audacity 2.0 & wxWidgets 2.8.
The Mark Silence dialog looks like:
Mark.png
Mark.png (51.34 KiB) Viewed 1163 times
Those two odd buttons ("Pristine" & "Tight") are just there to play around with.
The Trim Silence dialog looks like:
trim.png
trim.png (32.29 KiB) Viewed 1163 times
add this to FindClipping.h:

Code: Select all

/////////////////////////////////////////////////////////////////////
//Mark Silence Effect

#ifndef __AUDACITY_EFFECT_MARKSILENCE__
#define __AUDACITY_EFFECT_MARKSILENCE__

class wxString;

#include <wx/dialog.h>
#include <wx/intl.h>
#include <wx/radiobut.h>
#include <wx/textctrl.h>

#include "Effect.h"

class wxStaticText;
class wxRadioButton;
class wxTextCtrl;
class WaveTrack;

class EffectMarkSilence:public Effect
{
   friend class MarkSilenceDialog;

public:

   EffectMarkSilence();

   virtual wxString GetEffectName()
   {
      return wxString(_("Mark Silence..."));
   }

   virtual std::set<wxString> GetEffectCategories()
   {
      std::set<wxString> result;
      result.insert(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin"));
      return result;
   }

   virtual wxString GetEffectIdentifier()
   {
      return wxString(wxT("MarkSilence"));
   }

   virtual wxString GetEffectAction()
   {
      return wxString(_("Adding label(s) to mark silence(s)"));
   }

   virtual wxString GetEffectDescription();

   virtual bool PromptUser();
   virtual bool TransferParameters(Shuttle & shuttle);

   virtual bool Process();

private:
   bool ProcessOne(LabelTrack * pLabelTrack, LabelTrack * pNumericLabelTrack, WaveTrack * pWaveTrack,
      sampleCount pStart, sampleCount pLength);
   bool ProcessStereo(LabelTrack * pLabelTrack, LabelTrack * pNumericLabelTrack, WaveTrack * pLeftTrack, WaveTrack * pRightTrack,
      sampleCount pStart, sampleCount pLength);
   
   bool mbAddLeadingLabel;
   bool mbAddNumericLabelTrack;
   int miMinimumAmplitude;//Minimum amplitude (0 to USHRT_MAX - 65535) to define as silence (Absolute value of float sample value converted to an unsigned short)
   double mdMinimumDuration;//Minimum duration (seconds) to define as silent gap
   double mdMinimumWait;//Minimum Wait time (seconds) Before looking for another silent gap
};

//----------------------------------------------------------------------------
// MarkSilenceDialog
//----------------------------------------------------------------------------
class MarkSilenceDialog:public EffectDialog {
public:
   MarkSilenceDialog(EffectMarkSilence * effect, wxWindow * parent);

   void PopulateOrExchange(ShuttleGui & S);
   bool TransferDataFromWindow();

private:
   EffectMarkSilence *mEffect;
   wxCheckBox * mcbAddLeadingLabel;
   wxCheckBox * mcbAddNumericLabels;
   wxTextCtrl * mtcMinimumAmplitude;
   wxTextCtrl * mtcMinimumDuration;
   wxTextCtrl * mtcMinimumWait;
   wxButton * mbCleanVinyl;
   wxButton * mbDirtyVinyl;
   wxButton * mbCB;
   wxButton * mbPristine;
   wxButton * mbTight;
   wxButton * mbSave;
   wxButton * mbRestore;

   void OnCleanVinylButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnDirtyVinylButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnCBButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnPristineButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnTightButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnSaveDefaultsButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnRestoreDefaultsButton(wxCommandEvent & WXUNUSED(anEvent));

   DECLARE_EVENT_TABLE();
};
#endif // __AUDACITY_EFFECT_MARKSILENCE__

/////////////////////////////////////////////////////////////////////
//Trim Silence Front &/or Back Effect

#ifndef __AUDACITY_EFFECT_TRIMFRONTBACK__
#define __AUDACITY_EFFECT_TRIMFRONTBACK__

#define TRIM_FRONT 1
#define TRIM_BACK 2
#define TRIM_BOTH 3

class wxString;

#include <wx/dialog.h>
#include <wx/intl.h>
#include <wx/textctrl.h>

#include "Effect.h"

class wxStaticText;
class wxTextCtrl;
class WaveTrack;

class EffectTrimFrontBack:public Effect
{
   friend class TrimFrontBackDialog;

public:

   EffectTrimFrontBack();

   virtual wxString GetEffectName()
   {
      return wxString(_("Trim Silence Front and/or Back..."));
   }

   virtual std::set<wxString> GetEffectCategories()
   {
      std::set<wxString> result;
      result.insert(wxT("http://lv2plug.in/ns/lv2core#AnalyserPlugin"));
      return result;
   }

   virtual wxString GetEffectIdentifier()
   {
      return wxString(wxT("TrimFrontBack"));
   }

   virtual wxString GetEffectAction()
   {
      return wxString(_("Trim Silence Front and/or Back"));
   }

   virtual wxString GetEffectDescription();

   virtual bool PromptUser();
   virtual bool TransferParameters(Shuttle & shuttle);

   virtual bool Process();

private:
   bool ProcessOne(WaveTrack * pMonoTrack);
   bool ProcessStereo(WaveTrack * pLeftTrack, WaveTrack * pRightTrack);
   int miFrontBackBoth;
   int miFrontPad;
   int miBackPad;
   double mdMinimumAmplitude;//Minimum amplitude (0.0 to 1.0) to define as silence (Absolute value of float sample value)
};

//----------------------------------------------------------------------------
// TrimFrontBackDialog
//----------------------------------------------------------------------------
class TrimFrontBackDialog:public EffectDialog {
public:
   TrimFrontBackDialog(EffectTrimFrontBack * effect, wxWindow * parent);

   void PopulateOrExchange(ShuttleGui & S);
   bool TransferDataFromWindow();

private:
   EffectTrimFrontBack *mEffect;
   wxTextCtrl * mtcFrontPad;
   wxTextCtrl * mtcBackPad;
   wxTextCtrl * mtcMinimumAmplitude;
   wxTextCtrl * mtcFrom;
   wxButton * mbBoth;
   wxButton * mbFront;
   wxButton * mbBack;

   void OnBothButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnFrontButton(wxCommandEvent & WXUNUSED(anEvent));
   void OnBackButton(wxCommandEvent & WXUNUSED(anEvent));

   DECLARE_EVENT_TABLE();
};
#endif // __AUDACITY_EFFECT_TRIMFRONTBACK__
add this to FindClipping.cpp:

Code: Select all

/////////////////////////////////////////////////////////////////////
//Mark Silence Effect

EffectMarkSilence::EffectMarkSilence()
{
   SetEffectFlags(BUILTIN_EFFECT | ANALYZE_EFFECT);
   mbAddLeadingLabel = false;
   mbAddNumericLabelTrack = false;
   gPrefs->Read(wxT("/Effects/MarkSilence/RecentMinimumAmplitude"), &miMinimumAmplitude, 900);
   gPrefs->Read(wxT("/Effects/MarkSilence/RecentMinimumDuration"), &mdMinimumDuration, 2.7);
   gPrefs->Read(wxT("/Effects/MarkSilence/RecentMinimumWait"), &mdMinimumWait, 90.0);
}

wxString EffectMarkSilence::GetEffectDescription()
{
   return wxString::Format(_("Adding label(s) to mark silence(s)"));
}

bool EffectMarkSilence::PromptUser()
{
   MarkSilenceDialog dlg(this, mParent);
   dlg.CentreOnParent();

   if (dlg.ShowModal() == wxID_CANCEL) {
      return false;
   }
   gPrefs->Write(wxT("/Effects/MarkSilence/RecentAddLeadingLabel"), mbAddLeadingLabel);
   gPrefs->Write(wxT("/Effects/MarkSilence/RecentNumericLabelTrack"), mbAddNumericLabelTrack);
   gPrefs->Write(wxT("/Effects/MarkSilence/RecentMinimumAmplitude"), miMinimumAmplitude);
   gPrefs->Write(wxT("/Effects/MarkSilence/RecentMinimumDuration"), mdMinimumDuration);
   gPrefs->Write(wxT("/Effects/MarkSilence/RecentMinimumWait"), mdMinimumWait);

   return true;
}

bool EffectMarkSilence::TransferParameters(Shuttle & shuttle)
{
   shuttle.TransferBool(wxT("Leading"), mbAddLeadingLabel, false);
   shuttle.TransferBool(wxT("Numeric"), mbAddNumericLabelTrack, false);
   shuttle.TransferInt(wxT("Amplitude"), miMinimumAmplitude, 900);
   shuttle.TransferDouble(wxT("Duration"), mdMinimumDuration, 2.7);
   shuttle.TransferDouble(wxT("Wait"), mdMinimumWait, 90.0);

   return true;
}

bool EffectMarkSilence::Process()
{
   if (miMinimumAmplitude < 0) {
      miMinimumAmplitude = 0;
      wxMessageBox(_("the Minimum Amplitude value must NOT be negative; setting to 0"));
   }
   else if (miMinimumAmplitude > USHRT_MAX) {
      miMinimumAmplitude = USHRT_MAX;
      wxMessageBox(_("the Minimum Amplitude value must NOT exceed 65535; setting to 65535"));
   }

   LabelTrack * labelTrack = NULL;
   Track * original = NULL;
   LabelTrack * numericTrack = NULL;
   Track * originalNumeric = NULL;

   TrackListOfKindIterator iter(Track::Label, mTracks);
   for (Track * track = iter.First(); track; track = iter.Next()) {
      if (track->GetName() == wxT("Silences")) {
         labelTrack = (LabelTrack *)track;
         // copy LabelTrack here, so it can be undone on cancel
         labelTrack->Copy(labelTrack->GetStartTime(), labelTrack->GetEndTime(), &original);
         original->SetOffset(labelTrack->GetStartTime());
         original->SetName(wxT("Silences"));
         break;
      }
      if (track->GetName() == wxT("Numeric")) {
         numericTrack = (LabelTrack *)track;
         // copy Numeric LabelTrack here, so it can be undone on cancel
         numericTrack->Copy(numericTrack->GetStartTime(), numericTrack->GetEndTime(), &originalNumeric);
         originalNumeric->SetOffset(numericTrack->GetStartTime());
         originalNumeric->SetName(wxT("Numeric"));
         break;
      }
   }

   if (!labelTrack) {
      labelTrack = mFactory->NewLabelTrack();
      labelTrack->SetName(_("Silences"));
      mTracks->Add((Track *)labelTrack);
   }

   if (mbAddNumericLabelTrack && !numericTrack) {
      numericTrack = mFactory->NewLabelTrack();
      numericTrack->SetName(_("Numeric"));
      mTracks->Add((Track *)numericTrack);
   }

   // JC: Only process selected tracks.
   SelectedTrackListOfKindIterator waves(Track::Wave, mTracks);
   WaveTrack * waveTrack = (WaveTrack *)waves.First();
   while (waveTrack) {
      double trackStart = waveTrack->GetStartTime();
      double trackEnd = waveTrack->GetEndTime();
      double t0 = mT0 < trackStart ? trackStart : mT0;
      double t1 = mT1 > trackEnd ? trackEnd : mT1;

      if (t1 > t0) {
         sampleCount start = waveTrack->TimeToLongSamples(t0);
         sampleCount end = waveTrack->TimeToLongSamples(t1);
         sampleCount len = (sampleCount)(end - start);

         if (waveTrack->GetLinked()) {//Stereo track
            WaveTrack * rightTrack = (WaveTrack *)(waves.Next());
            if (!ProcessStereo(labelTrack, numericTrack, waveTrack, rightTrack, start, len)) {
               //put it back how it was
               mTracks->Remove((Track *)labelTrack);
               if (original) {
                  mTracks->Add((Track *)original);
               }
               if (originalNumeric) {
                  mTracks->Remove((Track *)numericTrack);
                  if (originalNumeric) {
                     mTracks->Add((Track *)originalNumeric);
                  }
               }
               return false;
            }
         }
         else {//Mono track
            if (!ProcessOne(labelTrack, numericTrack, waveTrack, start, len)) {
               //put it back how it was
               mTracks->Remove((Track *)labelTrack);
               if (original) {
                  mTracks->Add((Track *)original);
               }
               if (originalNumeric) {
                  mTracks->Remove((Track *)numericTrack);
                  if (originalNumeric) {
                     mTracks->Add((Track *)originalNumeric);
                  }
               }
               return false;
            }
         }
      }
      waveTrack = (WaveTrack *)waves.Next();
   }

   return true;
}

bool EffectMarkSilence::ProcessOne(LabelTrack * pLabelTrack, LabelTrack * pNumericLabelTrack, WaveTrack * pWaveTrack,
   sampleCount pStart, sampleCount pLength)
{
   double trackRate = pWaveTrack->GetRate();
   sampleCount duration = mdMinimumDuration * trackRate, waittime = mdMinimumWait * trackRate;
   if (duration >= pLength)
      return true;
   sampleCount lastUseful = pLength - duration;
   int labelCount = 2;
   wxString labelCountString;
   bool goodResult = true;

   float * buffer;
   try {
      buffer = new float[pLength];
   }
   catch (std::bad_alloc&) {
      wxMessageBox(_("Insufficient RAM to process sample"));
      return false;
   }

   if (mbAddLeadingLabel) {
      pLabelTrack->AddLabel(0.0, 0.0);
      if (pNumericLabelTrack) {
         pNumericLabelTrack->AddLabel(0.0, 0.0, wxT("1"));
      }
   }

   goodResult = pWaveTrack->Get((samplePtr)buffer, floatSample, pStart, pLength);
   if (!goodResult) {
      delete[] buffer;
      wxMessageBox(_("Unable to fill the track buffer"));
      return false;
   }
   sampleCount startLocation = pStart;
   if (pStart ==  0)
      startLocation = duration / 2;

   for (sampleCount bufferLocation = startLocation; bufferLocation < pLength; bufferLocation++) {
      if (fabs(buffer[bufferLocation]) <= miMinimumAmplitude) {
         for (sampleCount endLocation = bufferLocation + 1; endLocation < lastUseful; endLocation++) {
            if (fabs(buffer[endLocation]) >= miMinimumAmplitude) {
               if ((endLocation - bufferLocation) >= duration) {
                  double centerSilence = pWaveTrack->LongSamplesToTime(bufferLocation + ((endLocation - bufferLocation) / 2));
                  pLabelTrack->AddLabel(centerSilence, centerSilence);
                  if (pNumericLabelTrack) {
                     labelCountString.Printf(wxT("%d"), labelCount++);
                     pNumericLabelTrack->AddLabel(centerSilence, centerSilence, labelCountString);
                  }
                  bufferLocation = endLocation + waittime;
               }
               else
                  bufferLocation = endLocation + 1;
               break;
            }
         }
      }
   }

   delete[] buffer;

   return goodResult;
}

bool EffectMarkSilence::ProcessStereo(LabelTrack * pLabelTrack, LabelTrack * pNumericLabelTrack, WaveTrack * pLeftTrack, WaveTrack * pRightTrack,
   sampleCount pStart, sampleCount pLength) {
   double trackRate = pLeftTrack->GetRate();
   sampleCount duration = mdMinimumDuration * trackRate, waittime = mdMinimumWait * trackRate;
   if (duration >= pLength)
      return true;
   sampleCount lastUseful = pLength - duration;
   int labelCount = 2;
   wxString labelCountString;
   bool goodResult = true;

   unsigned short * buffer = NULL;
   float * leftBuffer = NULL;
   float * rightBuffer = NULL;
#define TEMPORARY_BUFFER_SIZE 2500000//Slightly less than one minute at 44,100 samples/second
   try {
      buffer = new unsigned short[pLength];
   }
   catch (std::bad_alloc&) {
      wxMessageBox(_("buffer - Insufficient RAM to process sample"));
      return false;
   }
   try {
      leftBuffer = new float[TEMPORARY_BUFFER_SIZE];
   }
   catch (std::bad_alloc&) {
      delete[] buffer;
      wxMessageBox(_("leftBuffer - Insufficient RAM to process sample"));
      return false;
   }
   try {
      rightBuffer = new float[TEMPORARY_BUFFER_SIZE];
   }
   catch (std::bad_alloc&) {
      delete[] buffer;
      delete[] leftBuffer;
      wxMessageBox(_("rightBuffer - Insufficient RAM to process sample"));
      return false;
   }

   if (mbAddLeadingLabel) {
      pLabelTrack->AddLabel(0.0, 0.0);
      if (pNumericLabelTrack) {
         pNumericLabelTrack->AddLabel(0.0, 0.0, wxT("1"));
      }
   }

   sampleCount startLocation = pStart;
   if (pStart ==  0)
      startLocation = duration / 2;
   sampleCount fullBuffers = ((pLength - startLocation) / TEMPORARY_BUFFER_SIZE), bufferRemainder = ((pLength - startLocation) % TEMPORARY_BUFFER_SIZE);

   for (sampleCount filler = 0; filler < fullBuffers; filler++) {
      //Assuming Track->Get() will never fail
      goodResult = pLeftTrack->Get((samplePtr)leftBuffer, floatSample, startLocation, TEMPORARY_BUFFER_SIZE);
      if (!goodResult) {
         delete[] buffer;
         delete[] leftBuffer;
         delete[] rightBuffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      goodResult = pRightTrack->Get((samplePtr)rightBuffer, floatSample, startLocation, TEMPORARY_BUFFER_SIZE);
      if (!goodResult) {
         delete[] buffer;
         delete[] leftBuffer;
         delete[] rightBuffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      for (sampleCount getAverage = 0; getAverage < TEMPORARY_BUFFER_SIZE; getAverage++) {
         float sampleAverage = (fabs(leftBuffer[getAverage]) + fabs(rightBuffer[getAverage])) / 2;
         unsigned short shortSampleAverage = sampleAverage * USHRT_MAX;//USHRT_MAX = 65535;
         buffer[(filler * TEMPORARY_BUFFER_SIZE) + getAverage] = shortSampleAverage;
      }
      startLocation  += TEMPORARY_BUFFER_SIZE;
   }
   goodResult = pLeftTrack->Get((samplePtr)leftBuffer, floatSample, startLocation, bufferRemainder);
   if (!goodResult) {
      delete[] buffer;
      delete[] leftBuffer;
      delete[] rightBuffer;
      wxMessageBox(_("Unable to fill the track buffer"));
      return false;
   }
   goodResult = pRightTrack->Get((samplePtr)rightBuffer, floatSample, startLocation, bufferRemainder);
   if (!goodResult) {
      delete[] buffer;
      delete[] leftBuffer;
      delete[] rightBuffer;
      wxMessageBox(_("Unable to fill the track buffer"));
      return false;
   }
   for (sampleCount getAverage = 0; getAverage < bufferRemainder; getAverage++) {
      float sampleAverage = (fabs(leftBuffer[getAverage]) + fabs(rightBuffer[getAverage])) / 2;
      unsigned short shortSampleAverage = sampleAverage * USHRT_MAX;//USHRT_MAX = 65535;
      buffer[(fullBuffers * TEMPORARY_BUFFER_SIZE) + getAverage] = shortSampleAverage;
   }

   startLocation = pStart;
   if (pStart ==  0)
      startLocation = duration / 2;
   for (sampleCount bufferLocation = startLocation; bufferLocation < pLength; bufferLocation++) {
      if (buffer[bufferLocation] <= miMinimumAmplitude) {
         bool weedOut = true;
#define WEED_GRANULARITY 1000
         for (sampleCount weedLocation = bufferLocation + 1; weedLocation < WEED_GRANULARITY; weedLocation++) {//Eliminate false positives
            if (buffer[weedLocation] > miMinimumAmplitude) {
               weedOut = false;
               weedLocation = WEED_GRANULARITY;
               bufferLocation  += (WEED_GRANULARITY - 1);
            }
         }
         if (weedOut) {
            for (sampleCount endLocation = bufferLocation + 1; endLocation < lastUseful; endLocation++) {
               if (buffer[endLocation] >= miMinimumAmplitude) {
                  if ((endLocation - bufferLocation) >= duration) {
                     double centerSilence = pLeftTrack->LongSamplesToTime(bufferLocation + ((endLocation - bufferLocation) / 2));
                     pLabelTrack->AddLabel(centerSilence, centerSilence);
                     if (pNumericLabelTrack) {
                        labelCountString.Printf(wxT("%d"), labelCount++);
                        pNumericLabelTrack->AddLabel(centerSilence, centerSilence, labelCountString);
                     }
                     bufferLocation = endLocation + waittime;
                  }
                  else
                     bufferLocation = endLocation + 1;
                  break;
               }
            }
         }
      }
   }

   delete[] rightBuffer;
   delete[] leftBuffer;
   delete[] buffer;

   return goodResult;
}

//----------------------------------------------------------------------------
// MarkSilenceDialog
//----------------------------------------------------------------------------
#define ID_CLEAN_VINYL 9000
#define ID_DIRTY_VINYL 9001
#define ID_CD 9002
#define ID_PRISTINE 9003
#define ID_TIGHT 9004
#define ID_SAVE_DEFAULTS 9005
#define ID_RESTORE_DEFAULTS 9006

BEGIN_EVENT_TABLE(MarkSilenceDialog, EffectDialog)
   EVT_BUTTON(ID_CLEAN_VINYL, MarkSilenceDialog::OnCleanVinylButton)
   EVT_BUTTON(ID_DIRTY_VINYL, MarkSilenceDialog::OnDirtyVinylButton)
   EVT_BUTTON(ID_CD, MarkSilenceDialog::OnCBButton)
   EVT_BUTTON(ID_PRISTINE, MarkSilenceDialog::OnPristineButton)
   EVT_BUTTON(ID_TIGHT, MarkSilenceDialog::OnTightButton)
   EVT_BUTTON(ID_SAVE_DEFAULTS, MarkSilenceDialog::OnSaveDefaultsButton)
   EVT_BUTTON(ID_RESTORE_DEFAULTS, MarkSilenceDialog::OnRestoreDefaultsButton)
END_EVENT_TABLE()

MarkSilenceDialog::MarkSilenceDialog(EffectMarkSilence * effect, wxWindow * parent)
   : EffectDialog(parent, _("Mark Silence"), INSERT_EFFECT)
{
   mEffect = effect;
   mcbAddLeadingLabel = NULL;
   mcbAddNumericLabels = NULL;
   mtcMinimumAmplitude = NULL;
   mtcMinimumDuration = NULL;
   mtcMinimumWait = NULL;
   mbCleanVinyl = NULL;
   mbDirtyVinyl = NULL;
   mbCB = NULL;
   mbPristine = NULL;
   mbTight = NULL;
   mbSave = NULL;
   mbRestore = NULL;

   Init();
}

void MarkSilenceDialog::PopulateOrExchange(ShuttleGui & S)
{
   S.StartScroller();
   S.StartVerticalLay();
   {
      S.StartStatic(_("Extra Labels"), 0);
      {
         mcbAddLeadingLabel = S.TieCheckBox(_("Place label at the beginning of the track"), mEffect->mbAddLeadingLabel);
         if (mEffect->mbAddLeadingLabel)
            mcbAddLeadingLabel->SetValue(true);
         mcbAddNumericLabels = S.TieCheckBox(_("Create second label track with numeric labels"), mEffect->mbAddNumericLabelTrack);
         if (mEffect->mbAddNumericLabelTrack)
            mcbAddNumericLabels->SetValue(true);
      }
      S.EndStatic();

      S.StartStatic(_("Recorded Media Presets"), 0);
      {
         S.StartMultiColumn(5, wxALIGN_LEFT);
         {
         mbCleanVinyl = S.Id(ID_CLEAN_VINYL).AddButton(_("Clean Vinyl"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
         mbDirtyVinyl = S.Id(ID_DIRTY_VINYL).AddButton(_("Dirty Vinyl"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
         mbCB = S.Id(ID_CD).AddButton(_("CD"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
         mbPristine = S.Id(ID_PRISTINE).AddButton(_("Pristine"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
         mbTight = S.Id(ID_TIGHT).AddButton(_("Tight"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
         }
         S.EndMultiColumn();
      }
      S.EndStatic();

      S.StartStatic(_("Variables"), 0);
      {
         S.StartMultiColumn(2, wxALIGN_LEFT);
         {
            mtcMinimumAmplitude = S.TieTextBox(_("Minimum amplitude (0 to 65535) to define as silence:"), mEffect->miMinimumAmplitude, 20);
            mtcMinimumAmplitude->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

            mtcMinimumDuration = S.TieTextBox(_("Minimum duration to define as silent gap (seconds):"), mEffect->mdMinimumDuration, 20);
            mtcMinimumDuration->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

            mtcMinimumWait = S.TieTextBox(_("Minimum wait time before looking for another silent gap (seconds):"), mEffect->mdMinimumWait, 20);
            mtcMinimumWait->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
         }
         S.EndMultiColumn();

         S.StartStatic(_("Defaults"), 0);
         {
            S.StartMultiColumn(2, wxALIGN_LEFT);
            {
               mbSave = S.Id(ID_SAVE_DEFAULTS).AddButton(_("Save As Defaults"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
               mbRestore = S.Id(ID_RESTORE_DEFAULTS).AddButton(_("Restore Defaults"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
            }
            S.EndMultiColumn();
         }
         S.EndStatic();
      }
      S.EndStatic();
   }
   S.EndVerticalLay();
   S.EndScroller();
}

bool MarkSilenceDialog::TransferDataFromWindow()
{
   EffectDialog::TransferDataFromWindow();
   return true;
}

void MarkSilenceDialog::OnCleanVinylButton(wxCommandEvent & WXUNUSED(anEvent)) {
   wxString value;
   value.Printf(wxT("%d"), 900);
   mtcMinimumAmplitude->SetValue(value);
   value.Printf(wxT("%.1f"), 2.7);
   mtcMinimumDuration->SetValue(value);
   value.Printf(wxT("%.1f"), 90.0);
   mtcMinimumWait->SetValue(value);
}

void MarkSilenceDialog::OnDirtyVinylButton(wxCommandEvent & WXUNUSED(anEvent)) {
   wxString value;
   value.Printf(wxT("%d"), 1200);
   mtcMinimumAmplitude->SetValue(value);
   value.Printf(wxT("%.1f"), 1.0);
   mtcMinimumDuration->SetValue(value);
   value.Printf(wxT("%.1f"), 90.0);
   mtcMinimumWait->SetValue(value);
}

void MarkSilenceDialog::OnCBButton(wxCommandEvent & WXUNUSED(anEvent)) {
   wxString value;
   value.Printf(wxT("%d"), 500);
   mtcMinimumAmplitude->SetValue(value);
   value.Printf(wxT("%.1f"), 1.0);
   mtcMinimumDuration->SetValue(value);
   value.Printf(wxT("%.1f"), 90.0);
   mtcMinimumWait->SetValue(value);
}

void MarkSilenceDialog::OnPristineButton(wxCommandEvent & WXUNUSED(anEvent)) {
   wxString value;
   value.Printf(wxT("%d"), 100);
   mtcMinimumAmplitude->SetValue(value);
   value.Printf(wxT("%.1f"), 0.03);
   mtcMinimumDuration->SetValue(value);
   value.Printf(wxT("%.1f"), 90.0);
   mtcMinimumWait->SetValue(value);
}

void MarkSilenceDialog::OnTightButton(wxCommandEvent & WXUNUSED(anEvent)) {
   wxString value;
   value.Printf(wxT("%d"), 200);
   mtcMinimumAmplitude->SetValue(value);
   value.Printf(wxT("%.1f"), 0.05);
   mtcMinimumDuration->SetValue(value);
   value.Printf(wxT("%.1f"), 90.0);
   mtcMinimumWait->SetValue(value);
}

void MarkSilenceDialog::OnSaveDefaultsButton(wxCommandEvent & WXUNUSED(anEvent)) {
   gPrefs->Write(wxT("/Effects/MarkSilence/AddLeadingLabel"), mcbAddLeadingLabel->GetValue());
   gPrefs->Write(wxT("/Effects/MarkSilence/AddNumericLabelTrack"), mcbAddNumericLabels->GetValue());
   gPrefs->Write(wxT("/Effects/MarkSilence/MinimumAmplitude"), mtcMinimumAmplitude->GetValue());
   gPrefs->Write(wxT("/Effects/MarkSilence/MinimumDuration"), mtcMinimumDuration->GetValue());
   gPrefs->Write(wxT("/Effects/MarkSilence/MinimumWait"), mtcMinimumWait->GetValue());
}

void MarkSilenceDialog::OnRestoreDefaultsButton(wxCommandEvent & WXUNUSED(anEvent)) {
   gPrefs->Read(wxT("/Effects/MarkSilence/AddLeadingLabel"), &mEffect->mbAddLeadingLabel, false);
   if (mEffect->mbAddLeadingLabel)
      mcbAddLeadingLabel->SetValue(true);
   gPrefs->Read(wxT("/Effects/MarkSilence/AddNumericLabelTrack"), &mEffect->mbAddNumericLabelTrack, false);
   if (mEffect->mbAddNumericLabelTrack)
      mcbAddNumericLabels->SetValue(true);
   wxString value;
   gPrefs->Read(wxT("/Effects/MarkSilence/MinimumAmplitude"), &mEffect->miMinimumAmplitude, 900);
   value.Printf(wxT("%d"), mEffect->miMinimumAmplitude);
   mtcMinimumAmplitude->SetValue(value);
   gPrefs->Read(wxT("/Effects/MarkSilence/MinimumDuration"), &mEffect->mdMinimumDuration, 2.7);
   value.Printf(wxT("%.1f"), mEffect->mdMinimumDuration);
   mtcMinimumDuration->SetValue(value);
   gPrefs->Read(wxT("/Effects/MarkSilence/MinimumWait"), &mEffect->mdMinimumWait, 90.0);
   value.Printf(wxT("%.1f"), mEffect->mdMinimumWait);
   mtcMinimumWait->SetValue(value);
}

/////////////////////////////////////////////////////////////////////
//Trim Silence Front &/or Back Effect

EffectTrimFrontBack::EffectTrimFrontBack()
{
   miFrontBackBoth = TRIM_BOTH;
   gPrefs->Read(wxT("/Effects/TrimFrontBack/FrontBackBoth"), &miFrontBackBoth, TRIM_BOTH);
   gPrefs->Read(wxT("/Effects/TrimFrontBack/FrontPad"), &miFrontPad, 22000);
   gPrefs->Read(wxT("/Effects/TrimFrontBack/BackPad"), &miBackPad, 22000);
   gPrefs->Read(wxT("/Effects/TrimFrontBack/MinimumAmplitude"), &mdMinimumAmplitude, 0.005);
}

wxString EffectTrimFrontBack::GetEffectDescription()
{
   return wxString::Format(_("Trim Silence Front and/or Back"));
}

bool EffectTrimFrontBack::PromptUser()
{
   TrimFrontBackDialog dlg(this, mParent);
   dlg.CentreOnParent();

   if (dlg.ShowModal() == wxID_CANCEL) {
      return false;
   }
   gPrefs->Write(wxT("/Effects/MarkSilence/FrontBackBoth"), miFrontBackBoth);
   gPrefs->Write(wxT("/Effects/MarkSilence/BackPad"), miFrontPad);
   gPrefs->Write(wxT("/Effects/MarkSilence/FrontPad"), miBackPad);
   gPrefs->Write(wxT("/Effects/MarkSilence/MinimumAmplitude"), mdMinimumAmplitude);

   return true;
}

bool EffectTrimFrontBack::TransferParameters(Shuttle & shuttle)
{
   shuttle.TransferInt(wxT("FrontBackBoth"), miFrontBackBoth, TRIM_BOTH);
   shuttle.TransferInt(wxT("FrontPad"), miFrontPad, 22000);
   shuttle.TransferInt(wxT("BackPad"), miBackPad, 22000);
   shuttle.TransferDouble(wxT("MinimumAmplitude"), mdMinimumAmplitude, 0.005);
   return true;
}

bool EffectTrimFrontBack::Process()
{
   if (mdMinimumAmplitude < 0.0) {
      mdMinimumAmplitude = 0.0;
      wxMessageBox(_("the Minimum Amplitude value must NOT be negative; setting to 0"));
   }
   else if (mdMinimumAmplitude > 1.0) {
      mdMinimumAmplitude = 1.0;
      wxMessageBox(_("the Minimum Amplitude value must NOT exceed 1.0; setting to 1"));
   }
   if (miFrontPad < 0) {
      miFrontPad = 0;
      wxMessageBox(_("the Front Pad value must NOT be negative; setting to 0"));
   }
   if (miBackPad < 0) {
      miBackPad = 0;
      wxMessageBox(_("the Back Pad value must NOT be negative; setting to 0"));
   }

   // JC: Only process selected tracks.
   SelectedTrackListOfKindIterator waves(Track::Wave, mTracks);
   WaveTrack * waveTrack = (WaveTrack *)waves.First();
   while (waveTrack) {
      double trackStart = waveTrack->GetStartTime();
      double trackEnd = waveTrack->GetEndTime();
      double t0 = mT0 < trackStart ? trackStart : mT0;
      double t1 = mT1 > trackEnd ? trackEnd : mT1;

      if (t1 > t0) {
         sampleCount start = waveTrack->TimeToLongSamples(t0);
         sampleCount end = waveTrack->TimeToLongSamples(t1);
         sampleCount length = (sampleCount)(end - start);

         if (waveTrack->GetLinked()) {//Stereo track
            WaveTrack * rightTrack = (WaveTrack *)(waves.Next());
            if (!ProcessStereo(waveTrack, rightTrack)) {
               return false;
            }
         }
         else {//Mono track
            if (!ProcessOne(waveTrack)) {
               return false;
            }
         }
      }
      waveTrack = (WaveTrack *)waves.Next();
   }

   return true;
}

bool EffectTrimFrontBack::ProcessOne(WaveTrack * pMonoTrack)
{
   bool goodResult = true;
   if ((miFrontBackBoth == TRIM_BOTH) || (miFrontBackBoth == TRIM_FRONT)) {
      sampleCount trackEndSample = pMonoTrack->TimeToLongSamples(pMonoTrack->GetEndTime());
      sampleCount trackStartSample = pMonoTrack->TimeToLongSamples(pMonoTrack->GetStartTime());

      float * buffer;
      try {
         buffer = new float[trackEndSample];
      }
      catch (std::bad_alloc&) {
         wxMessageBox(_("Insufficient RAM to process sample"));
         return false;
      }

      goodResult = pMonoTrack->Get((samplePtr)buffer, floatSample, trackStartSample, trackEndSample);
      if (!goodResult) {
         delete[] buffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      for (sampleCount bufferLocation = 0; bufferLocation < trackEndSample; bufferLocation++) {
         if (fabs(buffer[bufferLocation]) >= mdMinimumAmplitude) {
            if (bufferLocation > miFrontPad) {
               bufferLocation = bufferLocation - miFrontPad;
               pMonoTrack->Clear(pMonoTrack->GetStartTime(), pMonoTrack->LongSamplesToTime(bufferLocation));
            }
            break;
         }
      }
      delete[] buffer;
   }
   if ((miFrontBackBoth == TRIM_BOTH) || (miFrontBackBoth == TRIM_BACK)) {
      sampleCount trackEndSample = pMonoTrack->TimeToLongSamples(pMonoTrack->GetEndTime());
      sampleCount trackStartSample = pMonoTrack->TimeToLongSamples(pMonoTrack->GetStartTime());

      float * buffer;
      try {
         buffer = new float[trackEndSample];
      }
      catch (std::bad_alloc&) {
         wxMessageBox(_("Insufficient RAM to process sample"));
         return false;
      }

      goodResult = pMonoTrack->Get((samplePtr)buffer, floatSample, trackStartSample, trackEndSample);
      if (!goodResult) {
         delete[] buffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      sampleCount bufferEnd = trackEndSample - 1;
      for (sampleCount bufferLocation = bufferEnd; bufferLocation > trackStartSample; bufferLocation--) {
         if (fabs(buffer[bufferLocation]) >= mdMinimumAmplitude) {
            if ((bufferLocation + miBackPad) <= bufferEnd) {
               bufferLocation = bufferLocation + miBackPad;
               pMonoTrack->Clear(pMonoTrack->LongSamplesToTime(bufferLocation), pMonoTrack->GetEndTime());
            }
            break;
         }
      }
      delete[] buffer;
   }

   return goodResult;
}

bool EffectTrimFrontBack::ProcessStereo(WaveTrack * pLeftTrack, WaveTrack * pRightTrack) {
   bool goodResult = true;
   sampleCount leftFront = 0, rightFront = 0, leftBack = 0, rightBack = 0;

   if ((miFrontBackBoth == TRIM_BOTH) || (miFrontBackBoth == TRIM_FRONT)) {
      //Left track
      sampleCount trackEndSample = pLeftTrack->TimeToLongSamples(pLeftTrack->GetEndTime());
      sampleCount trackStartSample = pLeftTrack->TimeToLongSamples(pLeftTrack->GetStartTime());

      float * buffer;
      try {
         buffer = new float[trackEndSample];
      }
      catch (std::bad_alloc&) {
         wxMessageBox(_("Insufficient RAM to process sample"));
         return false;
      }

      goodResult = pLeftTrack->Get((samplePtr)buffer, floatSample, trackStartSample, trackEndSample);
      if (!goodResult) {
         delete[] buffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      for (sampleCount bufferLocation = 0; bufferLocation < trackEndSample; bufferLocation++) {
         if (fabs(buffer[bufferLocation]) >= mdMinimumAmplitude) {
            if (bufferLocation > miFrontPad) {
               bufferLocation = bufferLocation - miFrontPad;
               leftFront = bufferLocation;
            }
            break;
         }
      }
      delete[] buffer;

      //Right track
      trackEndSample = pRightTrack->TimeToLongSamples(pRightTrack->GetEndTime());
      trackStartSample = pRightTrack->TimeToLongSamples(pRightTrack->GetStartTime());

      try {
         buffer = new float[trackEndSample];
      }
      catch (std::bad_alloc&) {
         wxMessageBox(_("Insufficient RAM to process sample"));
         return false;
      }

      goodResult = pRightTrack->Get((samplePtr)buffer, floatSample, trackStartSample, trackEndSample);
      if (!goodResult) {
         delete[] buffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      for (sampleCount bufferLocation = 0; bufferLocation < trackEndSample; bufferLocation++) {
         if (fabs(buffer[bufferLocation]) >= mdMinimumAmplitude) {
            if (bufferLocation > miFrontPad) {
               bufferLocation = bufferLocation - miFrontPad;
               rightFront = bufferLocation;
            }
            break;
         }
      }
      delete[] buffer;
   }
   if ((miFrontBackBoth == TRIM_BOTH) || (miFrontBackBoth == TRIM_FRONT)) {
      if ((leftFront != 0) && (rightFront != 0)) {
         sampleCount front = leftFront;
         if (rightFront < front)
            front = rightFront;
         goodResult = pRightTrack->Clear(pRightTrack->GetStartTime(), pRightTrack->LongSamplesToTime(front));
         if (goodResult)
            goodResult = pLeftTrack->Clear(pLeftTrack->GetStartTime(), pLeftTrack->LongSamplesToTime(front));
      }
   }

   if ((miFrontBackBoth == TRIM_BOTH) || (miFrontBackBoth == TRIM_BACK)) {
      //Left track
      sampleCount trackEndSample = pLeftTrack->TimeToLongSamples(pLeftTrack->GetEndTime());
      sampleCount trackStartSample = pLeftTrack->TimeToLongSamples(pLeftTrack->GetStartTime());

      float * buffer;
      try {
         buffer = new float[trackEndSample];
      }
      catch (std::bad_alloc&) {
         wxMessageBox(_("Insufficient RAM to process sample"));
         return false;
      }

      goodResult = pLeftTrack->Get((samplePtr)buffer, floatSample, trackStartSample, trackEndSample);
      if (!goodResult) {
         delete[] buffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      sampleCount bufferEnd = trackEndSample - 1;
      for (sampleCount bufferLocation = bufferEnd; bufferLocation > trackStartSample; bufferLocation--) {
         if (fabs(buffer[bufferLocation]) >= mdMinimumAmplitude) {
            if ((bufferLocation + miBackPad) <= bufferEnd) {
               bufferLocation = bufferLocation + miBackPad;
               leftBack = bufferLocation;
            }
            break;
         }
      }
      delete[] buffer;

      //Right track
      trackEndSample = pRightTrack->TimeToLongSamples(pRightTrack->GetEndTime());
      trackStartSample = pRightTrack->TimeToLongSamples(pRightTrack->GetStartTime());

      try {
         buffer = new float[trackEndSample];
      }
      catch (std::bad_alloc&) {
         wxMessageBox(_("Insufficient RAM to process sample"));
         return false;
      }

      goodResult = pRightTrack->Get((samplePtr)buffer, floatSample, trackStartSample, trackEndSample);
      if (!goodResult) {
         delete[] buffer;
         wxMessageBox(_("Unable to fill the track buffer"));
         return false;
      }
      bufferEnd = trackEndSample - 1;
      for (sampleCount bufferLocation = bufferEnd; bufferLocation > trackStartSample; bufferLocation--) {
         if (fabs(buffer[bufferLocation]) >= mdMinimumAmplitude) {
            if ((bufferLocation + miBackPad) <= bufferEnd) {
               bufferLocation = bufferLocation + miBackPad;
               rightBack = bufferLocation;
            }
            break;
         }
      }
      delete[] buffer;
   }
   if (goodResult) {
      if ((miFrontBackBoth == TRIM_BOTH) || (miFrontBackBoth == TRIM_BACK)) {
         if ((leftBack != 0) && (rightBack != 0)) {
            sampleCount back = leftBack;
            if (rightBack > back)
               back = rightBack;
            sampleCount debugsc = pRightTrack->TimeToLongSamples(pRightTrack->GetEndTime());
            goodResult = pRightTrack->Clear(pRightTrack->LongSamplesToTime(back), pRightTrack->GetEndTime());
            if (goodResult)
               goodResult = pLeftTrack->Clear(pLeftTrack->LongSamplesToTime(back), pLeftTrack->GetEndTime());
         }
      }
   }

   return goodResult;
}

//----------------------------------------------------------------------------
// TrimFrontBackDialog
//----------------------------------------------------------------------------
#define ID_TRIM_BOTH 9003
#define ID_TRIM_FRONT 9001
#define ID_TRIM_BACK 9002

BEGIN_EVENT_TABLE(TrimFrontBackDialog, EffectDialog)
EVT_BUTTON(ID_TRIM_FRONT, TrimFrontBackDialog::OnFrontButton)
EVT_BUTTON(ID_TRIM_BACK, TrimFrontBackDialog::OnBackButton)
EVT_BUTTON(ID_TRIM_BOTH, TrimFrontBackDialog::OnBothButton)
END_EVENT_TABLE()

TrimFrontBackDialog::TrimFrontBackDialog(EffectTrimFrontBack * effect, wxWindow * parent)
: EffectDialog(parent, _("Mark Silence"), INSERT_EFFECT)
{
   mEffect = effect;
   mtcFrontPad = NULL;
   mtcBackPad = NULL;
   mtcMinimumAmplitude = NULL;
   mtcFrom = NULL;
   mbBoth = NULL;
   mbFront = NULL;
   mbBack = NULL;

   Init();
}

void TrimFrontBackDialog::PopulateOrExchange(ShuttleGui & S)
{
   S.StartScroller();
   S.StartVerticalLay();
   {
      mtcFrontPad = S.TieTextBox(_("Padding at front (in samples):"), mEffect->miFrontPad, 20);
      mtcFrontPad->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

      mtcBackPad = S.TieTextBox(_("Padding it back (in samples):"), mEffect->miBackPad, 20);
      mtcBackPad->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

      mtcMinimumAmplitude = S.TieTextBox(_("Minimum amplitude (0.0 to 1.0) to define as silence:"), mEffect->mdMinimumAmplitude, 20);
      mtcMinimumAmplitude->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

      S.StartStatic(_("Trim Silence From"), 0);
      {
         S.StartMultiColumn(3, wxALIGN_LEFT);
         {
            mbBoth = S.Id(ID_TRIM_BOTH).AddButton(_("Both"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
            mbFront = S.Id(ID_TRIM_FRONT).AddButton(_("Front"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
            mbBack = S.Id(ID_TRIM_BACK).AddButton(_("Back"), wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
         }
         S.EndMultiColumn();
         wxString fromWhere;
         if (mEffect->miFrontBackBoth == TRIM_FRONT)
            fromWhere = _("trimming from front");
         else if (mEffect->miFrontBackBoth == TRIM_BACK)
            fromWhere = _("trimming from back");
         else
            fromWhere = _("trimming from both");
         mtcFrom = S.AddTextBox(wxEmptyString, fromWhere, 20);
         mtcFrom->SetEditable(false);
      }
      S.EndStatic();
   }
   S.EndVerticalLay();
   S.EndScroller();
}

bool TrimFrontBackDialog::TransferDataFromWindow()
{
   EffectDialog::TransferDataFromWindow();
   return true;
}

void TrimFrontBackDialog::OnBothButton(wxCommandEvent & WXUNUSED(anEvent)) {
   mEffect->miFrontBackBoth = 3;
   mtcFrom->SetValue(_("trimming from both"));
}

void TrimFrontBackDialog::OnFrontButton(wxCommandEvent & WXUNUSED(anEvent)) {
   mEffect->miFrontBackBoth = 1;
   mtcFrom->SetValue(_("trimming from front"));
}

void TrimFrontBackDialog::OnBackButton(wxCommandEvent & WXUNUSED(anEvent)) {
   mEffect->miFrontBackBoth = 2;
   mtcFrom->SetValue(_("trimming from back"));
}
-Edgar
running Audacity personally customized 2.0.6 daily in a professional audio studio
occasionally using current Audacity alpha for testing and support situations
64-bit Windows Pro 10

Gale Andrews
Quality Assurance
Posts: 41761
Joined: Fri Jul 27, 2007 12:02 am
Operating System: Windows 10

Re: Mark Silence effect as C++ code

Post by Gale Andrews » Sat Dec 12, 2015 12:42 pm

Ed, I am not sure what the point of this is. Is it so that we can process longer selections? Is it meant to replace Silence Finder and Sound Finder?

Do we need yet another Trim Silence type of effect?

I do think we should bite the bullet of providing a unified silence and/or sound finder with more features than we have now. We should realise the danger that the many new users who obtain Audacity to digitize tapes and records need a simple interface. That should not stall development, even if it means retaining a simple silence finder.

I am not sure competing C++ and Nyquist versions will help, but C++ would let us hide advanced options in a single effect.


Gale
________________________________________FOR INSTANT HELP: (Click on Link below)
* * * * * Tips * * * * * Tutorials * * * * * Quick Start Guide * * * * * Audacity Manual

Edgar
Forum Crew
Posts: 2043
Joined: Thu Sep 03, 2009 9:13 pm
Operating System: Windows 10

Re: Mark Silence effect as C++ code

Post by Edgar » Sat Dec 12, 2015 4:07 pm

Gale Andrews wrote:I am not sure what the point of this is. Is it so that we can process longer selections? Is it meant to replace Silence Finder and Sound Finder?
Nyquist's effects are slow and severely limited by selection length; C++ effects are much faster and can (potentially) handle much larger selection. As you note below Nyquist also has some dialog limitations (multiple preset buttons, saving/loading user defaults etc.). As for replacing - I do not plan on doing so, in fact, I actually have multiple similar versions of Silence Finder (Nyquist) with slightly different names in my Analyze menu, each of which handles different kinds of source material (from: CD, vinyl, tape, internet radio etc.). Not only does the user have the ability to define a "user default" but the effect (separately also) stores its current options in Preferences.
Gale Andrews wrote:Do we need yet another Trim Silence type of effect?
I am not sure competing C++ and Nyquist versions will help, but C++ would let us hide advanced options in a single effect.
My effect is incredibly fast and works on extremely long selections. After a little experimentation one can find suitable values for the options and since the effect stores its options in Preferences the effect opens with the last used option values (even across reboots).

My real goal was speed – my code processes a 30 minute stereo track in about two seconds; similar Nyquist code requires more than 25 seconds. When used in a chain on a large number of files this can really add up!
-Edgar
running Audacity personally customized 2.0.6 daily in a professional audio studio
occasionally using current Audacity alpha for testing and support situations
64-bit Windows Pro 10

steve
Site Admin
Posts: 81609
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Mark Silence effect as C++ code

Post by steve » Sat Dec 12, 2015 4:43 pm

Edgar wrote: Nyquist's effects are slow and severely limited by selection length;
Rather a sweeping statement ;)
Nyquist effects tend to be slower than C++ effects, but not always the case (my "alias free square wave generator", programmed in Nyquist, is many times faster than the alias free square wave option in the C++ "Tone" generator).

"Some" Nyquist plug-ins are currently limited by selection length. This is usually due to a limitation in the Audacity implementation of Nyquist (how Audacity passes the audio track data to Nyquist) rather than in Nyquist itself, and it is fixable (bu not easily)

In the case of Silence Finder, I don't agree that it is "severely limited by selection length". It can happily work on multiple hours duration, though I do of course agree that it is quite slow on long selections, so speed improvements would be welcome (which can probably not be done in Nyquist without adversely affecting accuracy).
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

Edgar
Forum Crew
Posts: 2043
Joined: Thu Sep 03, 2009 9:13 pm
Operating System: Windows 10

Re: Mark Silence effect as C++ code

Post by Edgar » Sat Dec 12, 2015 8:47 pm

steve wrote:
Edgar wrote: Nyquist's effects are slow and severely limited by selection length;
Rather a sweeping statement ;)
I defer to Steve's expertise but, given the exact same coding paradigm (e.g. allocate some RAM, read samples from the track and write them into an array, single step from the beginning of the array through the array until a condition is met, use the array position to identify a sample-accurate time location on the track then delete everything from the beginning of the track to that position) I believe that it is physically impossible for any interpreted language (e.g. Nyquist) to be faster than properly written compiled code. This is especially important when chaining numerous files as the interpreter must be exercised repetitively.

As far as selection length and memory allocation, with Audacity's Nyquist can you do something on the lines of:

Code: Select all

boolean success = true
try {
allocate a huge buffer 
} 
catch {
success = false
}
if success = false then try to allocate 2 half-huge buffers
then keep doing that over and over again, halving the size of the buffer, until successful? Or simply ask the system how much RAM is available for memory allocation to your program then manage your buffers accordingly?

Obviously, these two effects are designed for a more commercial usage – specifically, transferring large libraries of analog material (vinyl, tape, CD etc.) into separate digital tracks. BTW, I am not really campaigning to have this code added to Audacity; when I write code which I find useful I always try to present it here in case someone in the future might find it useful.
-Edgar
running Audacity personally customized 2.0.6 daily in a professional audio studio
occasionally using current Audacity alpha for testing and support situations
64-bit Windows Pro 10

steve
Site Admin
Posts: 81609
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Mark Silence effect as C++ code

Post by steve » Sun Dec 13, 2015 5:35 am

I'm going somewhat off-topic here, but just as a point of interest, one of the things going for Nyquist is that, despite being an interpreted language, it has many built-in "primitives" written specifically for audio processing, that are written in compiled C. Where looping through samples can be done within these primitives, processing speed can be quite reasonable (though usually still a bit slower than when written in pure C++). on the other hand, looping through samples in an interpreted XLISP loop will be slow.

The memory / long selection problem in Nyquist plug-ins is that Audacity does not provide arbitrary access to the audio data. For example, if you want to normalize a selection, then it is necessary to analyze all of the audio to find the peak level before amplifying it, but Nyquist only has access to the audio via a single variable, which must remain in RAM so that it can be amplified after it has been analyzed. What is really needed is the ability for Nyquist to call blocks of data as required so that it can release memory after processing each block. This has been done as an experimental proof of concept, but we still don't yet have this in Audacity (standalone Nyquist does this automatically).
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

Edgar
Forum Crew
Posts: 2043
Joined: Thu Sep 03, 2009 9:13 pm
Operating System: Windows 10

Re: Mark Silence effect as C++ code

Post by Edgar » Mon Dec 14, 2015 12:40 am

I have found one bug in the way Preferences are handled, identified two algorithm enhancements and one new feature. It might be a few days before I can post the code.
-Edgar
running Audacity personally customized 2.0.6 daily in a professional audio studio
occasionally using current Audacity alpha for testing and support situations
64-bit Windows Pro 10

Locked