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

  Audacity: A Digital Audio Editor

  EffectEqualization.cpp

  Mitch Golden
  Vaughan Johnson (Preview)
  Martyn Shaw (FIR filters, response curve, graphic EQ)

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

\file Equalization.cpp
\brief Implements EffectEqualiztaion, EqualizationDialog,
EqualizationPanel, EQCurve and EQPoint.

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


\class EffectEqualization
\brief An Effect.

  Performs filtering, using an FFT to do a FIR filter.
  It lets the user draw an arbitrary envelope (using the same
  envelope editing code that is used to edit the track's
  amplitude envelope).

  Also allows the curve to be specified with a series of 'graphic EQ'
  sliders.

  The filter is applied using overlap/add of Hanning windows.

  Has presets of certain specific equalization curves, suitable
  for old recordings.

  Clone of the FFT Filter effect, no longer part of Audacity.

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

\class EqualizationDialog
\brief Dialog used with EffectEqualization

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

\class EqualizationPanel
\brief EqualizationPanel is used with EqualizationDialog and controls
a graph for EffectEqualization.  We should look at amalgamating the
various graphing code, such as provided by FreqWindow and FilterPanel.

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

\class EQCurve
\brief EQCurve is used with EffectEqualization.

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

\class EQPoint
\brief EQPoint is used with EQCurve and hence EffectEqualization.

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

#include "../Audacity.h"
#include "Equalization.h"
#include "../AColor.h"
#include "../ShuttleGui.h"
#include "../PlatformCompatibility.h"
#include "../FileNames.h"
#include "../Envelope.h"
#include "../FFT.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../WaveTrack.h"
#include "../widgets/Ruler.h"
#include "../xml/XMLFileReader.h"
#include "../Theme.h"
#include "../AllThemeResources.h"
#include "../WaveTrack.h"
#include "float_cast.h"
#include <vector>

#include <wx/bitmap.h>
#include <wx/button.h>
#include <wx/msgdlg.h>
#include <wx/brush.h>
#include <wx/dcmemory.h>
#include <wx/event.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/choice.h>
#include <wx/radiobut.h>
#include <wx/stattext.h>
#include <wx/string.h>
#include <wx/textdlg.h>
#include <wx/ffile.h>
#include <wx/filedlg.h>
#include <wx/filefn.h>
#include <wx/stdpaths.h>
#include <wx/settings.h>
#include <wx/checkbox.h>

#if wxUSE_TOOLTIPS
#include <wx/tooltip.h>
#endif
#include <wx/utils.h>

#include <math.h>

#include <wx/arrimpl.cpp>

WX_DEFINE_OBJARRAY( EQPointArray );
WX_DEFINE_OBJARRAY( EQCurveArray );

const double EqualizationDialog::thirdOct[] =
{
   20., 25., 31., 40., 50., 63., 80., 100., 125., 160., 200.,
   250., 315., 400., 500., 630., 800., 1000., 1250., 1600., 2000.,
   2500., 3150., 4000., 5000., 6300., 8000., 10000., 12500., 16000., 20000.,
};

const float EffectEqualization::curvex[] =
  {
   30., 31., 50., 63., 70., 100., 125., 200., 250., 300.,
   400., 500., 600., 700., 800., 900., 1000., 2000., 3000., 4000.,
   5000., 6000., 7000., 8000., 9000., 10000., 15000., 16000.
};

// Don't want to be warned about double->float precision loss here
// when compiling with MSVC.
#if defined(__WXMSW__)
#pragma warning( disable: 4305 )
#endif
const float EffectEqualization::curvey[][nCurvePoints] =
{
   {
   // amradio
//    30   31   50   63   70   100  125  200  250  300  400  500  600  700
      -20.,-20.,-20.,-20.,-20.,-20.,-16.,-12., -8., -4., 0.0, 0.0, 0.0, 0.0,
//  800  900  1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 15000 16000.
         0.0, 0.0, 0.0, -4., -8.,-12.,-16.,-20.0,-20.,-20.,-20.,-20., -20., -20.0
   },
   {
   // acoustic (see p 52)
      -20.0, -20.0, -20.0,   5.0,   4.4,   3.3,   2.5,   1.7,   0.0,   0.0,
      0.0,     0.0,   0.0,   0.0,   0.0,   0.0,   0.0,   0.0,  -1.5,  -2.5,
      -3.3,   -4.0,  -4.5,  -5.0, -10.0, -15.0, -20.0, -20.0
   },
   {
   // NAB
      20.0,   20.0,  16.0,  15.6,  15.5,  13.8,  12.0,   8.0,   6.2,   5.0,
      3.0,     1.7,   1.0,   0.0,  -0.5,  -1.0,  -1.3,  -4.2,  -6.5,  -8.5,
      -10.2, -12.0, -13.0, -14.0, -15.0, -16.0, -20.0, -20.0
   },
   {
   // LP
      13.5,   13.2,  13.0,  12.8,  12.5,  11.0,  10.5,   8.0,   7.0,   6.0,
      3.5,     2.5,   1.5,   1.0,   0.5,  -0.5,  -1.0,  -3.5,  -6.0,  -8.0,
      -10.0, -11.5, -12.5, -13.5, -14.5, -16.0, -21.2, -22.0
   },
   {
   // AES
      22.5,   22.5,  18.0,  16.0,  15.0,  12.0,  10.0,   6.5,   5.2,   4.5,
      3.0,     2.0,   1.5,   1.0,   0.5,   0.0,   0.0,  -2.2,  -4.0,  -5.5,
      -6.7,   -8.0,  -9.0, -10.0, -11.0, -12.0, -15.5, -16.0
   },
   {
   // Decca FFRR Micro
      14.0,   14.0,  14.0,  13.8,  13.5,  12.5,  11.5,   8.5,   7.2,   6.0,
      4.0,     2.5,   1.5,   1.0,   0.5,   0.0,   0.0,  -1.5,  -3.0,  -4.5,
      -6.0,   -7.0,  -8.0,  -8.5,  -9.0, -10.0, -12.6, -13.0
   },
   {
   // Decca FFRR 78
      22.0,   21.5,  14.0,  11.2,   9.8,   6.0,   2,0,   1.5,   1.0,   0.5,
      0.0,     0.0,   0.0,   0.0,   0.0,   0.0,   0.0,   0.0,   0.0   -0.5,
      -1.0,   -2.0,  -2.5,  -3.5,  -4.0,  -4.5,  -7.0,  -7.5
   },
   {
   // RIAA
      18.6,   18.5,  16.9,  15.9,  15.3,  13.1,  11.6,   8.2,   6.7,   5.5,
      3.8,     2.6,   1.8,   1.2,   0.8,   0.3,   0.0,  -2.6,  -4.7,  -6.6,
      -8.2,   -9.6, -10.8, -11.9, -12.9, -13.7, -17.2, -17.7
   },
   {
   // Col 78
      16.0,   16.0,  16.0,  14.0,  12.5,  10.0,   8.5,   5.0,   4.0,   3.0,
      2.0,     1.0,   0.5,   0.2,   0.0,  -0.5,  -1.0,  -3.5,  -6.0,  -8.0,
      -10.0, -11.5, -12.5, -13.5, -14.5, -16.0, -21.2, -22.0
   },
   {
   // Decca FFRR LP
      17.5,   17.2,  14.0,  12.0,  11.5,   9.0,   7.5,   5.0,   4.0,   3.0,
      2.0,     1.5,   1.0,   0.7,   0.2,   0.0,   0.0,  -4.0,  -6.7,  -8.5,
      -10.0, -11.0, -12.0, -13.0, -13.2, -14.0, -16.0, -16.0
   },
   {
   // EMI 78
      14.0,   14.0,  14.0,  12.0,  11.0,   8.0,   7.0,   4.0,   3.0,   2.0,
      1.0,     0.5,   0.0,   0.0,   0.0,   0.0,   0.0,   0.0,   0.0,   0.0,
      0.0,     0.0,   0.0,   0.0,   0.0,   0.0,  -5.0,  -5.0
   },
   {
   // RCA Victor 1938
      24.0,   24.0,  24.0,  21.8,  20.0,  16.0,  13.0,   9.0,   7.5,   6.0,
      4.0,     3.0,   2.0,   1.5,   1.0,   0.5,   0.0,  -2.5,  -5.0,  -6.5,
      -7.5,   -8.0,  -7.7,  -7.5,  -7.5,  -7.5,  -7.5,  -7.5
   },
   {
   // RCA Victor 1947
      24.0,   24.0,  24.0,  21.8,  20.0,  16.0,  13.0,   9.0,   7.5,   6.0,
      4.0,     3.0,   2.0,   1.5,   1.0,   0.5,   0.0,  -2.5,  -5.0,  -6.5,
      -8.0,  -10.0, -11.5, -12.0, -12.5, -12.5, -12.5, -12.5
   }
  };
#if defined(__WXMSW__)
#pragma warning( default: 4305 )
#endif

// LLL:  This seem to have a strange interaction with wxWidgets 2.8.9.  It's
//       possible that the wxSlider was fixed as it seems to work ok for me
//       under Ubuntu 8.10 with self build wxWidgets 2.8.9.
//
//       Leaving here for now...just disabling
#ifdef __WXGTK__disabled
/*
 * wxSlider exhibits strange behaviour on wxGTK when wxSL_INVERSE and/or
 * negative scale values are used. This affects at least SUSE 9.3 with
 * self-compiled wxWidgets 2.6.2 and Kubuntu 6.06. This class is used
 * to transparently work around those problems. Use wxSliderBugfix wherever
 * you would have used a slider with wxSL_INVERSE and/or negative scale
 * values.
 *
 * The equalization class uses wxSliderBugfix everywhere even when not
 * strictly needed. This is important because we override some wxSlider
 * functions which are not virtual, and so we need to cast to wxSliderBugfix
 * instead of to wxSlider to allow the right function to be called statically.
 */
class wxSliderBugfix : public wxSlider
{
public:
   wxSliderBugfix(wxWindow* parent, wxWindowID id, int value,
                  int minValue, int maxValue,
                  const wxPoint& point = wxDefaultPosition,
                  const wxSize& size = wxDefaultSize,
                  long style = wxSL_HORIZONTAL,
                  const wxValidator& validator = wxDefaultValidator,
                  const wxString& name = wxT("slider")) :
         wxSlider(parent, id, 0, 0, maxValue - minValue, point,
                  size, style & ~wxSL_INVERSE, validator, name) {
      isInverse = (style & wxSL_INVERSE) != 0;
      originalMinValue = minValue;
      SetValue(value);
   }

   int GetMin() const {
      return originalMinValue;
   }

   int GetMax() const {
      return wxSlider::GetMax() + originalMinValue;
   }

   int GetValue() const {
      if (isInverse)
         return wxSlider::GetMax() - wxSlider::GetValue() + originalMinValue;
      else
         return wxSlider::GetValue() + originalMinValue;
   }

   void SetValue(int value) {
      if (isInverse)
         wxSlider::SetValue(wxSlider::GetMax() - value + originalMinValue);
      else
         wxSlider::SetValue(value - originalMinValue);
   }

private:
   bool isInverse;
   int originalMinValue;
};

#else

/*
 * On platforms other than wxGTK, the workaround is not needed
 */
#define wxSliderBugfix wxSlider

#endif // ifdef __WXGTK__

const wxChar * EffectEqualization::curveNames[] =
  {
    wxT("amradio"),
    wxT("acoustic"),
    wxT("NAB"),
    wxT("Columbia LP"),
    wxT("AES"),
    wxT("Decca FFRR Micro"),
    wxT("Decca FFRR 78"),
    wxT("RIAA"),
    wxT("Columbia 78"),
    wxT("Decca FFRR LP"),
    wxT("EMI 78"),
    wxT("RCA Victor 1938"),
    wxT("RCA Victor 1947")
  };

#define NUM_INTERP_CHOICES 3
static wxString interpChoiceStrings[NUM_INTERP_CHOICES];

void EffectEqualization::ReadPrefs()
{

   gPrefs->Read(wxT("/CsPresets/EQFilterLength"),
         &mM, 4001);
   if ((mM < 21) || (mM > 8191)) {  // corrupted Prefs?
      mM = 4001;  //default
      gPrefs->Write(wxT("/CsPresets/EQFilterLength"), mM);
   }
   gPrefs->Read(wxT("/CsPresets/EQCurveName"), &mCurveName, wxT("custom"));
   gPrefs->Read(wxT("/CsPresets/EQLin"), &mLin, false);
   gPrefs->Read(wxT("/CsPresets/EQdBMin"), &mdBMin, -30.0);
   if ((mdBMin < -120) || (mdBMin > -10)) {  // corrupted Prefs?
      mdBMin = -30;  //default
      gPrefs->Write(wxT("/CsPresets/EQFilterLength"), mdBMin);
   }
   gPrefs->Read(wxT("/CsPresets/EQdBMax"), &mdBMax, 30.);
   if ((mdBMax < 0) || (mdBMax > 60)) {  // corrupted Prefs?
      mdBMax = 30;  //default
      gPrefs->Write(wxT("/CsPresets/EQFilterLength"), mdBMax);
   }
   gPrefs->Read(wxT("/CsPresets/EQDrawMode"), &mDrawMode, true);
   gPrefs->Read(wxT("/CsPresets/EQInterp"), &mInterp, 0);
   gPrefs->Read(wxT("/CsPresets/EQDrawGrid"), &mDrawGrid, true);
}

EffectEqualization::EffectEqualization()
{
   hFFT = InitializeFFT(windowSize);
   mFFTBuffer = new float[windowSize];
   mFilterFuncR = new float[windowSize];
   mFilterFuncI = new float[windowSize];
   ReadPrefs();

   mPrompting = false;

   interpChoiceStrings[0] = _("B-spline");
   interpChoiceStrings[1] = _("Cosine");
   interpChoiceStrings[2] = _("Cubic");
}


EffectEqualization::~EffectEqualization()
{
   if(hFFT) 
      EndFFT(hFFT);
   hFFT = NULL;
   if(mFFTBuffer)
      delete[] mFFTBuffer;
   mFFTBuffer = NULL;
   if(mFilterFuncR)
      delete[] mFilterFuncR;
   if(mFilterFuncI)
      delete[] mFilterFuncI;
   mFilterFuncR = NULL;
   mFilterFuncI = NULL;
}

bool EffectEqualization::Init()
{
   return(true);
}

bool EffectEqualization::PromptUser()
{
   // Detect whether we are editing a batch chain by checking the parent window
   mEditingBatchParams = (mParent != GetActiveProject());
   if (!mEditingBatchParams)
   {
      ReadPrefs();
   }

   TrackListOfKindIterator iter(Track::Wave, mTracks);
   WaveTrack *t = (WaveTrack *) iter.First();
   float hiFreq;
   if (t)
      hiFreq = ((float)(t->GetRate())/2.);
   else
      hiFreq = ((float)(GetActiveProject()->GetRate())/2.);


   EqualizationDialog dlog(this, ((double)loFreqI), hiFreq, mFilterFuncR, mFilterFuncI,
                           windowSize, mCurveName, mEditingBatchParams, mParent, -1, _("Equalization"));

   dlog.M = mM;
   dlog.curveName = mCurveName;
   dlog.linCheck = mLin;
   dlog.dBMin = mdBMin;
   dlog.dBMax = mdBMax;
   dlog.drawMode = mDrawMode;
   dlog.interp = mInterp;
   dlog.drawGrid = mDrawGrid;
   dlog.CentreOnParent();

   mPrompting = true;   // true when previewing, false in batch
   dlog.ShowModal();
   mPrompting = false;

   if (!dlog.GetReturnCode())
      return false;

   mM = dlog.M;
   mCurveName = dlog.curveName;
   mLin = dlog.linCheck;
   mdBMin = dlog.dBMin;
   mdBMax = dlog.dBMax;
   mDrawMode = dlog.drawMode;
   mInterp = dlog.interp;
   mDrawGrid = dlog.drawGrid;

   if (!mEditingBatchParams)
   {
      gPrefs->Write(wxT("/CsPresets/EQFilterLength"),mM);
      gPrefs->Write(wxT("/CsPresets/EQCurveName"),mCurveName);
      gPrefs->Write(wxT("/CsPresets/EQLin"),mLin);
      gPrefs->Write(wxT("/CsPresets/EQdBMin"),mdBMin);
      gPrefs->Write(wxT("/CsPresets/EQdBMax"),mdBMax);
      gPrefs->Write(wxT("/CsPresets/EQDrawMode"),mDrawMode);
      gPrefs->Write(wxT("/CsPresets/EQInterp"), mInterp);
      gPrefs->Write(wxT("/CsPresets/EQDrawGrid"), mDrawGrid);
   }

   return true;
}

bool EffectEqualization::DontPromptUser()
{
   TrackListOfKindIterator iter(Track::Wave, mTracks);
   WaveTrack *t = (WaveTrack *) iter.First();
   float hiFreq;
   if (t)
      hiFreq = ((float)(t->GetRate())/2.);
   else
      hiFreq = ((float)(GetActiveProject()->GetRate())/2.);

   EqualizationDialog dlog(this, ((double)loFreqI), hiFreq, mFilterFuncR, mFilterFuncI,
                           windowSize, mCurveName, false, NULL, -1, _("Equalization"));

   dlog.M = mM;
   dlog.curveName = mCurveName;
   dlog.linCheck = false;
   dlog.interp = mInterp;
   // IS required here - no call to ShowModal!
   dlog.TransferDataToWindow();
   dlog.CalcFilter();   // is needed here - the one done due to TransferDataToWindow->OnLinFreq isn't reliable

   return true;
}

bool EffectEqualization::TransferParameters( Shuttle & shuttle )
{
   shuttle.TransferInt(wxT("FilterLength"),mM,4001);
   shuttle.TransferString(wxT("CurveName"),mCurveName,wxT("eric"));
   shuttle.TransferBool(wxT("InterpolateLin"),mLin,false);
   shuttle.TransferEnum(wxT("interpolationMethod"),mInterp, NUM_INTERP_CHOICES,interpChoiceStrings);

   if(!mPrompting)
      DontPromptUser();   // not previewing, ie batch mode or initial setup
   return true;
}

bool EffectEqualization::Process()
{
   this->CopyInputTracks(); // Set up mOutputTracks.
   bool bGoodResult = true;

   SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
   WaveTrack *track = (WaveTrack *) iter.First();
   int count = 0;
   while (track) {
      double trackStart = track->GetStartTime();
      double trackEnd = track->GetEndTime();
      double t0 = mT0 < trackStart? trackStart: mT0;
      double t1 = mT1 > trackEnd? trackEnd: mT1;

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

         if (!ProcessOne(count, track, start, len))
         {
            bGoodResult = false;
            break;
         }
      }

      track = (WaveTrack *) iter.Next();
      count++;
   }

   this->ReplaceProcessedTracks(bGoodResult); 
   return bGoodResult;
}


bool EffectEqualization::ProcessOne(int count, WaveTrack * t,
                                 sampleCount start, sampleCount len)
{
   // create a new WaveTrack to hold all of the output, including 'tails' each end
   AudacityProject *p = GetActiveProject();
   WaveTrack *output = p->GetTrackFactory()->NewWaveTrack(floatSample, t->GetRate());

   int L = windowSize - (mM - 1);   //Process L samples at a go
   sampleCount s = start;
   sampleCount idealBlockLen = t->GetMaxBlockSize() * 4;
   if (idealBlockLen % L != 0)
      idealBlockLen += (L - (idealBlockLen % L));

   float *buffer = new float[idealBlockLen];

   float *window1 = new float[windowSize];
   float *window2 = new float[windowSize];
   float *thisWindow = window1;
   float *lastWindow = window2;

   sampleCount originalLen = len;

   int i,j;
   for(i=0; i<windowSize; i++)
      lastWindow[i] = 0;

   TrackProgress(count, 0.);
	bool bLoopSuccess = true;
   int wcopy = 0;
   int offset = (mM - 1)/2;

   while(len)
   {
      sampleCount block = idealBlockLen;
      if (block > len)
         block = len;

      t->Get((samplePtr)buffer, floatSample, s, block);

      for(i=0; i<block; i+=L)   //go through block in lumps of length L
      {
         wcopy = L;
         if (i + wcopy > block)   //if last lump would exceed block
            wcopy = block - i;   //shorten it
         for(j=0; j<wcopy; j++)
            thisWindow[j] = buffer[i+j];   //copy the L (or remaining) samples
         for(j=wcopy; j<windowSize; j++)
            thisWindow[j] = 0;   //this includes the padding

         Filter(windowSize, thisWindow);

         // Overlap - Add
         for(j=0; (j<mM-1) && (j<wcopy); j++)
            buffer[i+j] = thisWindow[j] + lastWindow[L + j];
         for(j=mM-1; j<wcopy; j++)
            buffer[i+j] = thisWindow[j];

         float *tempP = thisWindow;
         thisWindow = lastWindow;
         lastWindow = tempP;
      }  //next i, lump of this block

      output->Append((samplePtr)buffer, floatSample, block);
      len -= block;
      s += block;

      if (TrackProgress(count, (s-start)/(double)originalLen))
      {
			bLoopSuccess = false;
			break;
		}
   }

   if(bLoopSuccess)
   {
      // mM-1 samples of 'tail' left in lastWindow, get them now
      if(wcopy < (mM-1)) {
         // Still have some overlap left to process
         // (note that lastWindow and thisWindow have been exchanged at this point
         //  so that 'thisWindow' is really the window prior to 'lastWindow')
         for(j=0; j<mM-1-wcopy; j++)
            buffer[j] = lastWindow[wcopy + j] + thisWindow[L + wcopy + j];
         // And fill in the remainder after the overlap
         for( ; j<mM-1; j++)
            buffer[j] = lastWindow[wcopy + j];
      } else {
         for(j=0; j<mM-1; j++)
            buffer[j] = lastWindow[wcopy + j];
      }
      output->Append((samplePtr)buffer, floatSample, mM-1);
      output->Flush();

      // now move the appropriate bit of the output back to the track
      // (this could be enhanced in the future to use the tails)
      double offsetT0 = t->LongSamplesToTime((sampleCount)offset);
      double lenT = t->LongSamplesToTime(originalLen);
      // 'start' is the sample offset in 't', the passed in track
      // 'startT' is the equivalent time value
      // 'output' starts at zero
      double startT = t->LongSamplesToTime(start);
      
      //output has one waveclip for the total length, even though 
      //t might have whitespace seperating multiple clips
      //we want to maintain the original clip structure, so
      //only paste the intersections of the new clip.
      
      //Find the bits of clips that need replacing
      std::vector<std::pair<double, double> > clipStartEndTimes;
      std::vector<std::pair<double, double> > clipRealStartEndTimes; //the above may be truncated due to a clip being partially selected
      for (WaveClipList::compatibility_iterator it=t->GetClipIterator(); it; it=it->GetNext())
      {
         WaveClip *clip;
         double clipStartT;
         double clipEndT;
         
         clip = it->GetData();
         clipStartT = clip->GetStartTime();
         clipEndT = clip->GetEndTime();
         if( clipEndT <= startT )
            continue;   // clip is not within selection
         if( clipStartT >= startT + lenT )
            continue;   // clip is not within selection
            
         //save the actual clip start/end so that we can rejoin them after we paste.
         clipRealStartEndTimes.push_back(std::pair<double,double>(clipStartT,clipEndT));            
            
         if( clipStartT < startT )  // does selection cover the whole clip?
            clipStartT = startT; // don't copy all the new clip
         if( clipEndT > startT + lenT )  // does selection cover the whole clip?
            clipEndT = startT + lenT; // don't copy all the new clip
         
         //save them
         clipStartEndTimes.push_back(std::pair<double,double>(clipStartT,clipEndT));
      }
      //now go thru and replace the old clips with new
      for(unsigned int i=0;i<clipStartEndTimes.size();i++)
      {
         Track *toClipOutput;
         //remove the old audio and get the new
         t->Clear(clipStartEndTimes[i].first,clipStartEndTimes[i].second);
         output->Copy(clipStartEndTimes[i].first-startT+offsetT0,clipStartEndTimes[i].second-startT+offsetT0, &toClipOutput);   
         if(toClipOutput)
         {
            //put the processed audio in
            t->Paste(clipStartEndTimes[i].first,toClipOutput);
            //if the clip was only partially selected, the Paste will have created a split line.  Join is needed to take care of this
            //This is not true when the selection is fully contained within one clip (second half of conditional)
            if( (clipRealStartEndTimes[i].first  != clipStartEndTimes[i].first || 
                 clipRealStartEndTimes[i].second != clipStartEndTimes[i].second) &&
                 !(clipRealStartEndTimes[i].first <= startT &&  
                 clipRealStartEndTimes[i].second >= startT+lenT) )
               t->Join(clipRealStartEndTimes[i].first,clipRealStartEndTimes[i].second);
            delete toClipOutput;
         }
      }
   }

   delete[] buffer;
   delete[] window1;
   delete[] window2;
   delete output;

   return bLoopSuccess;
}

void EffectEqualization::Filter(sampleCount len,
         float *buffer)
{
   int i;
   float re,im;
   // Apply FFT
   RealFFTf(buffer, hFFT);
   //FFT(len, false, inr, NULL, outr, outi);

   // Apply filter
   // DC component is purely real
   mFFTBuffer[0] = buffer[0] * mFilterFuncR[0];
   for(i=1; i<(len/2); i++)
   {
      re=buffer[hFFT->BitReversed[i]  ];
      im=buffer[hFFT->BitReversed[i]+1];
      mFFTBuffer[2*i  ] = re*mFilterFuncR[i] - im*mFilterFuncI[i];
      mFFTBuffer[2*i+1] = re*mFilterFuncI[i] + im*mFilterFuncR[i];
   }
   // Fs/2 component is purely real
   mFFTBuffer[1] = buffer[1] * mFilterFuncR[len/2];

   // Inverse FFT and normalization
   InverseRealFFTf(mFFTBuffer, hFFT);
   ReorderToTime(hFFT, mFFTBuffer, buffer);
}


//----------------------------------------------------------------------------
// EqualizationPanel
//----------------------------------------------------------------------------

BEGIN_EVENT_TABLE(EqualizationPanel, wxPanel)
    EVT_PAINT(EqualizationPanel::OnPaint)
    EVT_MOUSE_EVENTS(EqualizationPanel::OnMouseEvent)
    EVT_MOUSE_CAPTURE_LOST(EqualizationPanel::OnCaptureLost)
    EVT_SIZE(EqualizationPanel::OnSize)
END_EVENT_TABLE()

EqualizationPanel::EqualizationPanel( double loFreq, double hiFreq,
                     Envelope *env,
                     EqualizationDialog *parent,
                     float *filterFuncR, float *filterFuncI, long windowSize,
                     wxWindowID id, const wxPoint& pos, const wxSize& size):
   wxPanel(parent, id, pos, size)
{
   mOutr = NULL;
   mOuti = NULL;

   mBitmap = NULL;
   mWidth = 0;
   mHeight = 0;
   mLoFreq = loFreq;
   mHiFreq = hiFreq;
   M = 21;  // should be odd
   mWindowSize = windowSize;
   mFilterFuncR = filterFuncR;
   mFilterFuncI = filterFuncI;
   mParent = parent;

   mEnvelope = env;
   mEnvelope->Flatten(0.);
   mEnvelope->Mirror(false);
   mEnvelope->SetTrackLen(1.0);

   RecalcRequired = true;
}

EqualizationPanel::~EqualizationPanel()
{
   if (mBitmap)
      delete mBitmap;
   if (mOuti)
      delete [] mOuti;
   if (mOutr)
      delete [] mOutr;
}

void EqualizationPanel::Recalc()
{
   if (mOutr)
      delete [] mOutr;
   mOutr = new float[mWindowSize];

   if (mOuti)
      delete [] mOuti;
   mOuti = new float[mWindowSize];

   mParent->CalcFilter();   //to calculate the actual response
#ifdef EXPERIMENTAL_USE_REALFFTF
   InverseRealFFT(mWindowSize, mFilterFuncR, mFilterFuncI, mOutr);
#else
   FFT(mWindowSize,true,mFilterFuncR,mFilterFuncI,mOutr,mOuti);   //work out FIR response - note mOuti will be all zeros
#endif // EXPERIMENTAL_USE_REALFFTF
}

void EqualizationPanel::OnSize(wxSizeEvent & evt)
{
   Refresh( false );
}

void EqualizationPanel::OnPaint(wxPaintEvent & evt)
{
   wxPaintDC dc(this);
   if(RecalcRequired) {
      Recalc();
      RecalcRequired = false;
   }
   int width, height;
   GetSize(&width, &height);

   if (!mBitmap || mWidth!=width || mHeight!=height)
   {
      if (mBitmap)
         delete mBitmap;

      mWidth = width;
      mHeight = height;
      mBitmap = new wxBitmap(mWidth, mHeight);
   }

   wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));

   wxMemoryDC memDC;
   memDC.SelectObject(*mBitmap);

   wxRect bkgndRect;
   bkgndRect.x = 0;
   bkgndRect.y = 0;
   bkgndRect.width = mWidth;
   bkgndRect.height = mHeight;
   memDC.SetBrush(bkgndBrush);
   memDC.SetPen(*wxTRANSPARENT_PEN);
   memDC.DrawRectangle(bkgndRect);

   bkgndRect.y = mHeight;
   memDC.DrawRectangle(bkgndRect);

   wxRect border;
   border.x = 0;
   border.y = 0;
   border.width = mWidth;
   border.height = mHeight;

   memDC.SetBrush(*wxWHITE_BRUSH);
   memDC.SetPen(*wxBLACK_PEN);
   memDC.DrawRectangle(border);

   mEnvRect = border;
   mEnvRect.Deflate(2, 2);

   // Pure blue x-axis line
   memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID));
   int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5);
   AColor::Line(memDC,
                mEnvRect.GetLeft(), mEnvRect.y + center,
                mEnvRect.GetRight(), mEnvRect.y + center);

   // Med-blue envelope line
   memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 3, wxSOLID));

   // Draw envelope
   double *values = new double[mEnvRect.width];
   mEnvelope->GetValues(values, mEnvRect.width, 0.0, 1.0/mEnvRect.width);
   int x, y, xlast = 0, ylast = 0;
   bool off = false, off1 = false;
   for(int i=0; i<mEnvRect.width; i++)
   {
      x = mEnvRect.x + i;
      y = lrint(mEnvRect.height*((dBMax-values[i])/(dBMax-dBMin)) + .25 ); //needs more optimising, along with'what you get'?
      if( y >= mEnvRect.height)
      {
         y = mEnvRect.height - 1;
         off = true;
      }
      else
      {
         off = false;
         off1 = false;
      }
      if ( (i != 0) & (!off1) )
      {
         AColor::Line(memDC, xlast, ylast,
                      x, mEnvRect.y + y);
      }
      off1 = off;
      xlast = x;
      ylast = mEnvRect.y + y;
   }
   delete[] values;

   //Now draw the actual response that you will get.
   //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
   memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 1, wxSOLID));
   double scale = (double)mEnvRect.height/(dBMax-dBMin);   //pixels per dB
   double yF;   //gain at this freq
   double delta = mHiFreq/(((double)mWindowSize/2.));   //size of each freq bin

   bool lin = mParent->mFaderOrDraw[0]->GetValue() & mParent->mLinFreq->IsChecked();   // log or lin scale?

   double loLog = log10(mLoFreq);
   double step = lin ? mHiFreq : (log10(mHiFreq) - loLog);
   step /= ((double)mEnvRect.width-1.);
   double freq;   //actual freq corresponding to x position
   int halfM = (M-1)/2;
   int n;   //index to mFreqFunc
   for(int i=0; i<mEnvRect.width; i++)
   {
      x = mEnvRect.x + i;
      freq = lin ? step*i : pow(10., loLog + i*step);   //Hz
      if( ( lin ? step : (pow(10., loLog + (i+1)*step)-freq) ) < delta)
      {   //not enough resolution in FFT
         freq = M_PI*freq/mHiFreq;   //radians, normalized
         yF = 0.;
         for(int j=0;j<halfM;j++)
         {
            yF += 2. * mOutr[j] * cos(freq*(halfM-j));
         }
         yF += mOutr[halfM];
         yF = fabs(yF);
         if(yF!=0.)
            yF = 20.0*log10(yF);   //20 here as an amplitude
         else
            yF = dBMin;
      }
      else
      {   //use FFT, it has enough resolution
         n = (int)(freq/delta + .5);
         if(pow(mFilterFuncR[n],2)+pow(mFilterFuncI[n],2)!=0.)
            yF = 10.0*log10(pow(mFilterFuncR[n],2)+pow(mFilterFuncI[n],2));   //10 here, a power
         else
            yF = dBMin;
      }
      if(yF < dBMin)
         yF = dBMin;
      yF = center-scale*yF;
      if(yF>mEnvRect.height)
         yF = mEnvRect.height - 1;
      if(yF<0.)
         yF=0.;
      y = (int)(yF+.5);

      if (i != 0)
      {
         AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y);
      }
      xlast = x;
      ylast = mEnvRect.y + y;
   }

   memDC.SetPen(*wxBLACK_PEN);
   if( mParent->mFaderOrDraw[0]->GetValue() )
      mEnvelope->DrawPoints(memDC, mEnvRect, 0.0, mEnvRect.width, false, dBMin, dBMax);

   if( mParent->drawGrid )
   {
      mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height+2, true, true, 0, 1);
      mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width+2, true, true, 1, 2);
   }

   dc.Blit(0, 0, mWidth, mHeight,
           &memDC, 0, 0, wxCOPY, FALSE);
}

void EqualizationPanel::OnMouseEvent(wxMouseEvent & event)
{
   if (event.ButtonDown() && !HasCapture())
   {
      CaptureMouse();
   }

   if (mEnvelope->MouseEvent(event, mEnvRect, 0.0, mEnvRect.width, false,
            dBMin, dBMax, dBMin, dBMax))
   {
      mParent->EnvelopeUpdated();
      RecalcRequired = true;
      Refresh(false);
   }

   if (event.ButtonUp() && HasCapture())
   {
      ReleaseMouse();
   }
}

void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & event)
{
   if (HasCapture())
   {
      ReleaseMouse();
   }
}


// WDR: class implementations

//----------------------------------------------------------------------------
// EqualizationDialog
//----------------------------------------------------------------------------

// WDR: event table for EqualizationDialog

BEGIN_EVENT_TABLE(EqualizationDialog,wxDialog)
   EVT_SIZE( EqualizationDialog::OnSize )
   EVT_PAINT( EqualizationDialog::OnPaint )
   EVT_ERASE_BACKGROUND( EqualizationDialog::OnErase )

   EVT_SLIDER( ID_LENGTH, EqualizationDialog::OnSliderM )
   EVT_SLIDER( ID_DBMAX, EqualizationDialog::OnSliderDBMAX )
   EVT_SLIDER( ID_DBMIN, EqualizationDialog::OnSliderDBMIN )
   EVT_COMMAND_RANGE( ID_SLIDER,
                      ID_SLIDER + NUMBER_OF_BANDS - 1,
                      wxEVT_COMMAND_SLIDER_UPDATED,
                      EqualizationDialog::OnSlider)
   EVT_CHOICE( ID_INTERP, EqualizationDialog::OnInterp )

   EVT_CHOICE( ID_CURVE, EqualizationDialog::OnCurve )
   EVT_BUTTON( ID_SAVEAS, EqualizationDialog::OnSaveAs )
   EVT_BUTTON( ID_DELETE, EqualizationDialog::OnDelete )
   EVT_BUTTON( ID_CLEAR, EqualizationDialog::OnClear )
#ifdef EXPERIMENTAL_EQ_INVERT
   EVT_BUTTON( ID_INVERT, EqualizationDialog::OnInvert )
#endif

   EVT_BUTTON( ID_EFFECT_PREVIEW, EqualizationDialog::OnPreview )
   EVT_BUTTON( wxID_OK, EqualizationDialog::OnOk )
   EVT_BUTTON( wxID_CANCEL, EqualizationDialog::OnCancel )
   EVT_RADIOBUTTON(drawRadioID, EqualizationDialog::OnDrawRadio)
   EVT_RADIOBUTTON(sliderRadioID, EqualizationDialog::OnSliderRadio)
   EVT_CHECKBOX(ID_LIN_FREQ, EqualizationDialog::OnLinFreq)
   EVT_CHECKBOX(GridOnOffID, EqualizationDialog::OnGridOnOff)
END_EVENT_TABLE()

EqualizationDialog::EqualizationDialog(EffectEqualization * effect,
                     double loFreq, double hiFreq,
                     float *filterFuncR,
                     float *filterFuncI,
                     long windowSize,
                     wxString curveName,
                     bool disallowCustom,
                     wxWindow *parent, wxWindowID id,
                     const wxString &title,
                     const wxPoint &position,
                     const wxSize& size,
                     long style):
   wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER | wxMAXIMIZE_BOX ),
   mDisallowCustom(disallowCustom),
   mCustomBackup(wxT("custom"))
{
   m_pEffect = effect;

   M = 4001;
   linCheck = false;
   dBMin = -30.;
   dBMax = 30;

#if wxUSE_TOOLTIPS
   wxToolTip::Enable(true);
#endif

   mLogEnvelope = new Envelope();
   mLogEnvelope->SetInterpolateDB(false);
   mLogEnvelope->Mirror(false);

   mLinEnvelope = new Envelope();
   mLinEnvelope->SetInterpolateDB(false);
   mLinEnvelope->Mirror(false);

   mLoFreq = loFreq;
   mHiFreq = hiFreq;

   mFilterFuncR = filterFuncR;
   mFilterFuncI = filterFuncI;
   mWindowSize = windowSize;

   mCurve = NULL;
   mDirty = false;

   // Load the EQ curves
   LoadCurves();
   if (mDisallowCustom)
   {
      mCustomBackup.Name = wxT("custom");
      EQCurve &realCustom = mCurves[mCurves.GetCount()-1];
      wxASSERT(realCustom.Name.IsSameAs(wxT("custom")));
      mCustomBackup.points = realCustom.points;
   }

   // Create the dialog
   MakeEqualizationDialog();

   // Note: initial curve is set in TransferDataToWindow

   bandsInUse = NUMBER_OF_BANDS;
   //double loLog = log10(mLoFreq);
   //double stepLog = (log10(mHiFreq) - loLog)/((double)NUM_PTS-1.);
   for(int i=0; i<NUM_PTS-1; i++)
      whens[i] = (double)i/(NUM_PTS-1.);
   whens[NUM_PTS-1] = 1.;
   whenSliders[NUMBER_OF_BANDS] = 1.;
   m_EQVals[NUMBER_OF_BANDS] = 0.;
}

EqualizationDialog::~EqualizationDialog()
{
   delete mLogEnvelope;
   delete mLinEnvelope;
}

//
// Load external curves with fallback to internal
//
void EqualizationDialog::LoadCurves()
{
   // Construct default curve filename
   //
   // LLL:  Wouldn't you know that as of WX 2.6.2, there is a conflict
   //       between wxStandardPaths and wxConfig under Linux.  The latter
   //       creates a normal file as "$HOME/.audacity", while the former
   //       expects the ".audacity" portion to be a directory.
   wxFileName fn( FileNames::DataDir(), wxT("EQCurves.xml") );

   // If it doesn't exist...
   if( !fn.FileExists() )
   {
      // Fallback to internal
      LoadDefaultCurves();
      return;
   }

   // Start from scratch
   mCurves.Clear();

   // Load the curves
   XMLFileReader reader;
   if( !reader.Parse( this, fn.GetFullPath() ) )
   {
      // Inform user of load failure
      wxMessageBox( reader.GetErrorStr(),
                    _("Error loading EQ curve"),
                    wxOK | wxCENTRE,
                    this );

      // Fallback to internal
      LoadDefaultCurves();
   }

   return;
}

//
// Load the internal curves
//
void EqualizationDialog::LoadDefaultCurves()
{
   int curve;
   int point;

   // Start from scratch
   mCurves.Clear();

   // For all internally defined curves...
   for( curve = 0; curve < EffectEqualization::nCurveTypes; curve++ )
   {
      // Create and name a new curve class
      mCurves.Add( wxString( EffectEqualization::curveNames[ curve ] ) );

      // For all points within this curve...
      for( point = 0; point < EffectEqualization::nCurvePoints; point++ )
      {
         // Extract the frequency and dB values
         double freq = EffectEqualization::curvex[ point ];
         double db   = EffectEqualization::curvey[ curve ][ point ];

         // Add it to the list of points within the curve
         mCurves[ curve ].points.Add( EQPoint( freq, db ) );
      }
   }
   mCurves.Add( _("custom") );

   SaveCurves();

   return;
}

//
// Save curves to external file
//
void EqualizationDialog::SaveCurves()
{
   if (mDisallowCustom)
      RevertCustom();

   // Construct default curve filename
   //
   // LLL:  Wouldn't you know that as of WX 2.6.2, there is a conflict
   //       between wxStandardPaths and wxConfig under Linux.  The latter
   //       creates a normal file as "$HOME/.audacity", while the former
   //       expects the ".audacity" portion to be a directory.
   wxFileName fn( FileNames::DataDir(), wxT("EQCurves.xml") );

   // If the directory doesn't exist...
   if( !fn.DirExists() )
   {
      // Attempt to create it
      if( !fn.Mkdir( fn.GetPath(), 511, wxPATH_MKDIR_FULL ) )
      {
         // MkDir() will emit message
         return;
      }
   }

   // Create/Open the file
   XMLFileWriter eqFile;

   try
   {
      eqFile.Open( fn.GetFullPath(), wxT("wb") );

      // Write the curves
      WriteXML( eqFile );

      // Close the file
      eqFile.Close();
   }
   catch (XMLFileWriterException* pException)
   {
      wxMessageBox(wxString::Format(
         _("Couldn't write to file \"%s\": %s"),
         fn.GetFullPath().c_str(), pException->GetMessage().c_str()),
         _("Error saving equalization curves"), wxICON_ERROR, this);

      delete pException;
   }
}

//
// Create the Equalization dialog
//
void EqualizationDialog::MakeEqualizationDialog()
{
   wxStaticText *txt;
   wxButton *btn;

   // Create the base sizer
   szrV = new wxBoxSizer( wxVERTICAL );

   // -------------------------------------------------------------------
   // ROW 1: Banner
   // -------------------------------------------------------------------
   txt = new wxStaticText(this, wxID_ANY,
         /* i18n-hint: && in here is an escape character to get a single & 
          * on screen, so keep it as is */
                          _("Equalization, by Martyn Shaw && Mitch Golden"));
   szrV->Add( txt, 0, wxALIGN_CENTRE|wxALL, 4 );

   // -------------------------------------------------------------------
   // ROW 2: EQ panel and sliders for vertical scale
   // -------------------------------------------------------------------
   szr1 = new wxFlexGridSizer( 4, 0, 0 );
   szr1->AddGrowableCol( 2, 1 );
   szr1->AddGrowableRow( 0, 1 );
   szr1->SetFlexibleDirection( wxBOTH );

   szr2 = new wxBoxSizer( wxVERTICAL );
   dBMaxSlider = new wxSliderBugfix(this, ID_DBMAX, 30, 0, 60,
                           wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE);
   szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 );
   dBMinSlider = new wxSliderBugfix(this, ID_DBMIN, -30, -120, -10,
                           wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE);
   szr2->Add( dBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 );
   szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 );

#if wxUSE_ACCESSIBILITY
   dBMaxSlider->SetName(_("Max dB"));
   dBMaxSlider->SetAccessible(new SliderAx(dBMaxSlider, wxString(wxT("%d ")) + _("dB")));
   dBMinSlider->SetName(_("Min dB"));
   dBMinSlider->SetAccessible(new SliderAx(dBMinSlider, wxString(wxT("%d ")) + _("dB")));
#endif

   dBRuler = new RulerPanel(this, wxID_ANY);
   dBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
   dBRuler->ruler.SetOrientation(wxVERTICAL);
   dBRuler->ruler.SetRange(60.0, -120.0);
   dBRuler->ruler.SetFormat(Ruler::LinearDBFormat);
   dBRuler->ruler.SetUnits(_("dB"));
   dBRuler->ruler.SetLabelEdges(true);
   int w, h;
   dBRuler->ruler.GetMaxSize(&w, NULL);
   dBRuler->SetSize(wxSize(w, 150));  // height needed for wxGTK

   szr4 = new wxBoxSizer( wxVERTICAL );
   szr4->AddSpacer(2); // vertical space for panel border and thickness of line
   szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL );
   szr4->AddSpacer(1); // vertical space for thickness of line
   szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL );

   mPanel = new EqualizationPanel( mLoFreq, mHiFreq,
                                   mLogEnvelope,
                                   this,
                                   mFilterFuncR, mFilterFuncI, mWindowSize,
                                   ID_FILTERPANEL);
   szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE|wxRIGHT, 4);
   szr3 = new wxBoxSizer( wxVERTICAL );
   szr1->Add( szr3, 0, wxALIGN_CENTRE|wxRIGHT, 0);   //spacer for last EQ

   /// Next row of wxFlexGridSizer
   szr1->Add(1, 1); // horizontal spacer
   szr1->Add(1, 1); // horizontal spacer

   freqRuler  = new RulerPanel(this, wxID_ANY);
   freqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes
   freqRuler->ruler.SetOrientation(wxHORIZONTAL);
   freqRuler->ruler.SetLog(true);
   freqRuler->ruler.SetRange(mLoFreq, mHiFreq);
   freqRuler->ruler.SetFormat(Ruler::IntFormat);
   freqRuler->ruler.SetUnits(_("Hz"));
   freqRuler->ruler.SetFlip(true);
   freqRuler->ruler.SetLabelEdges(true);
   freqRuler->ruler.GetMaxSize(NULL, &h);
   freqRuler->SetMinSize(wxSize(-1, h));
   szr1->Add( freqRuler, 0, wxEXPAND|wxALIGN_LEFT|wxRIGHT, 4 );

   szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 );

   // -------------------------------------------------------------------
   // ROW 3: Graphic EQ - this gets laid out horizontally in onSize
   // -------------------------------------------------------------------

   szrG = new wxBoxSizer( wxHORIZONTAL  );
   szrG->Add(0, 0, 0); // horizontal spacer, will be used to position LH EQ slider
   for (int i = 0; thirdOct[i] <= mHiFreq; ++i)
   {
      if( i == NUMBER_OF_BANDS )
         break;
      m_sliders[i] = new wxSliderBugfix(this, ID_SLIDER + i, 0, -20, +20,
                        wxDefaultPosition, wxSize(20, 124), wxSL_VERTICAL|
                         wxSL_INVERSE);
      szrG->Add( m_sliders[i], 0, wxEXPAND|wxALIGN_CENTER );
      szrG->Add(0, 0, 0); // horizontal spacer - used to put EQ sliders in correct position
      m_sliders[i]->Connect(wxEVT_ERASE_BACKGROUND,wxEraseEventHandler(EqualizationDialog::OnErase));
      m_sliders_old[i] = 0;
      m_EQVals[i] = 0.;

#if wxUSE_ACCESSIBILITY
      wxString name;
      if( thirdOct[i] < 1000.)
         name.Printf(wxString(wxT("%d ")) + _("Hz"), (int)thirdOct[i]);
      else
         name.Printf(wxString(wxT("%g ")) + _("kHz"), thirdOct[i]/1000.);
      m_sliders[i]->SetName(name);
      m_sliders[i]->SetAccessible(new SliderAx(m_sliders[i], wxString(wxT("%d ")) + _("dB")));
#endif
   }
   szrV->Add( szrG, 0, wxEXPAND|wxALIGN_LEFT|wxALL, 0 );

   wxSizerItem *EQslider = szrG->GetItem((size_t)1);
   wxSize EQsliderSize = EQslider->GetSize();   //size of the sliders
   szr3->SetMinSize(EQsliderSize.x/2, -1);   //extra gap for last slider

   // -------------------------------------------------------------------
   // ROW 4a: Graphic or curve drawing?
   // -------------------------------------------------------------------
   szrH = new wxBoxSizer( wxHORIZONTAL );

   mFaderOrDraw[0] = new wxRadioButton(
         this, drawRadioID, _("Draw curves"),
         wxDefaultPosition, wxDefaultSize, wxRB_GROUP );
   szrH->Add( mFaderOrDraw[0], 0, wxRIGHT, 10 );

   mFaderOrDraw[1] = new wxRadioButton(
         this, sliderRadioID, _("Graphic EQ"),
         wxDefaultPosition, wxDefaultSize, 0 );
   szrH->Add( mFaderOrDraw[1], 0, wxRIGHT, 4 );

   mInterpChoice = new wxChoice(this, ID_INTERP,
                             wxDefaultPosition, wxDefaultSize,
                             NUM_INTERP_CHOICES, interpChoiceStrings);

   mInterpChoice->SetSelection(0);
   szrI = new wxBoxSizer( wxHORIZONTAL );
   szrI->Add( mInterpChoice, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 40 );
   szrH->Add( szrI );

   szrL = new wxBoxSizer( wxHORIZONTAL );
   mLinFreq = new wxCheckBox(this, ID_LIN_FREQ, _("Linear frequency scale"));
   szrL->Add( mLinFreq, 0 );
   szrH->Add(szrL);  // either szrI or szrL are visible, not both.

   // -------------------------------------------------------------------
   // ROW 4b: Filter length grouping
   // -------------------------------------------------------------------

   // length of filter (M) label
   txt = new wxStaticText(this, wxID_ANY, _("Length of filter:"));
   szrH->Add( txt, 0 );

   // length of filter (M) slider
   MSlider = new wxSliderBugfix(this, ID_LENGTH, (M -1)/2, 10, 4095,
                           wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL);
   szrH->Add( MSlider, 0, wxEXPAND );

   wxString label;
   label.Printf( wxT("%d"), M );
   mMText = new wxStaticText(this, wxID_ANY, label);
   szrH->Add( mMText, 0 );

   // Add the length / graphic / draw grouping
   szrV->Add( szrH, 0, wxALIGN_CENTER | wxALL, 4 );

   // -------------------------------------------------------------------
   // ROW 5: Curve management grouping
   // -------------------------------------------------------------------
   szrC = new wxBoxSizer( wxHORIZONTAL );   //szrC is for the curves bits

   txt = new wxStaticText( this, wxID_ANY, _("Select curve:") );
   szrC->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 );

   // Create the choice sizer (helps in recreating choice control)
   mCurveSizer = new wxBoxSizer( wxHORIZONTAL );
   szrC->Add( mCurveSizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 );

   // Create the choice control
   CreateChoice();

   mSaveAs = new wxButton( this, ID_SAVEAS, _("Save As...") );
   szrC->Add( mSaveAs, 0, wxALIGN_CENTRE|wxLEFT, 4 );

   mDelete = new wxButton( this, ID_DELETE, _("Delete") );
   szrC->Add( mDelete, 0, wxALIGN_CENTRE|wxLEFT, 4 );

   btn = new wxButton( this, ID_CLEAR, _("Flat"));
   szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 );
#ifdef EXPERIMENTAL_EQ_INVERT
   btn = new wxButton( this, ID_INVERT, _("Invert"));
   szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 );
#endif
   mGridOnOff = new wxCheckBox(this, GridOnOffID, _("Grids"),
                            wxDefaultPosition, wxDefaultSize,
                            wxALIGN_RIGHT);
   mGridOnOff->SetName(_("Grids"));
   szrC->Add( mGridOnOff, 0, wxALIGN_CENTRE | wxALL, 4 );

   szrV->Add( szrC, 0, wxALIGN_CENTER | wxALL, 0 );

   // -------------------------------------------------------------------
   // ROW 6: Preview, OK, & Cancel buttons
   // -------------------------------------------------------------------
   szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND);

   // -------------------------------------------------------------------
   // Display now
   // -------------------------------------------------------------------
   SetAutoLayout(false);

   szrV->Show(szrG,true);
   szrH->Show(szrI,true);
   szrH->Show(szrL,false);

   SetSizerAndFit( szrV );
   SetSizeHints(GetSize());
   szrL->SetMinSize( szrI->GetSize() );

   szrV->Show(szrC,true);
   szrV->Show(szrG,false);
   szrH->Show(szrI,false);
   szrH->Show(szrL,true);

   return;
}

//
// (Re)Create the choice control
//
void EqualizationDialog::CreateChoice()
{
   wxChoice *choice;
   int i;
   int numCurves = mCurves.GetCount();
   wxString *curveNames = new wxString[ numCurves ];

   // Create an array of names
   for( i = 0; i < numCurves; i++ )
   {
     curveNames[ i ] = mCurves[ i ].Name;
   }

   // Create the control
   choice = new wxChoice( this, ID_CURVE,
                          wxDefaultPosition, wxDefaultSize,
                          numCurves, curveNames );

   // Have an existing control?
   if( mCurve )
   {
      // Then detach it from its sizer and delete it
      mCurveSizer->Detach( mCurve );
      delete mCurve;
   }

   // Save control ptr and add to its sizer
   mCurve = choice;
   mCurveSizer->Add( mCurve, 0 );

   // Delete the array of names
   delete [] curveNames;
}

//
// Validate data
//
bool EqualizationDialog::Validate()
{
   // If editing a batch chain, we don't want to be using the custom curve so
   // we offer to save it.
   while (mDisallowCustom && curveName.IsSameAs(wxT("custom")))
   {
      wxMessageBox(_("To use this EQ curve in a batch chain, please choose a new name for it."),
            _("EQ Curve needs a different name"),
            wxOK | wxCENTRE,
            this);

      if (!SaveAs())
         return false;
   }
   return true;
}

//
// Populate the window with relevant variables
//
bool EqualizationDialog::TransferDataToWindow()
{
   // Set log or lin freq scale (affects interpolation as well)
   mLinFreq->SetValue( linCheck );
   wxCommandEvent dummyEvent;
   OnLinFreq(dummyEvent);  // causes a CalcFilter

   mGridOnOff->SetValue( drawGrid ); // checks/unchecks the box on the interface

   MSlider->SetValue((M-1)/2);
   M = 0;                        // force refresh in TransferDataFromWindow()

   dBMinSlider->SetValue((int)dBMin);
   dBMin = 0;                     // force refresh in TransferDataFromWindow()

   dBMaxSlider->SetValue((int)dBMax);
   dBMax = 0;                    // force refresh in TransferDataFromWindow()

   // Set initial curve
   setCurve( curveName );

   // Set graphic interpolation mode
   mInterpChoice->SetSelection(interp);

   // Set Graphic (Fader) or Draw mode
   if(drawMode)
   {
      mFaderOrDraw[0]->SetValue(true);
   }
   else
   {
      mFaderOrDraw[1]->SetValue(true);
      UpdateGraphic();
   }

   return TransferDataFromWindow();
}

//
// Retrieve data from the window
//
bool EqualizationDialog::TransferDataFromWindow()
{   // Read the sliders and send to the panel
   wxString tip;

   bool rr = false;
   int dB = dBMinSlider->GetValue();
   if (dB != dBMin) {
      rr = true;
      dBMin = dB;
      mPanel->dBMin = dBMin;
#if wxUSE_TOOLTIPS
      tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMin);
      dBMinSlider->SetToolTip(tip);
#endif
   }

   dB = dBMaxSlider->GetValue();
   if (dB != dBMax) {
      rr = true;
      dBMax = dB;
      mPanel->dBMax = dBMax;
#if wxUSE_TOOLTIPS
      tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMax);
      dBMaxSlider->SetToolTip(tip);
#endif
   }

   // Refresh ruler if values have changed
   if (rr) {
      int w1, w2, h;
      dBRuler->ruler.GetMaxSize(&w1, &h);
      dBRuler->ruler.SetRange(dBMax, dBMin);
      dBRuler->ruler.GetMaxSize(&w2, &h);
      if( w1 != w2 )   // Reduces flicker
      {
         dBRuler->SetSize(wxSize(w2,h));
         szr1->Layout();
         LayoutEQSliders();
         szrG->Layout();
         freqRuler->Refresh(false);
      }
      dBRuler->Refresh(false);
   }

   int m = 2 * MSlider->GetValue() + 1;   // odd numbers only
   if (m != M) {
      M = m;
      mPanel->M = M;
      mMText->SetLabel(wxString::Format(wxT("%d"), M));
#if wxUSE_TOOLTIPS
      tip.Printf(wxT("%d"), M);
      MSlider->SetToolTip(tip);
#endif
   }

   mPanel->Refresh(false);

   return true;
}

bool EqualizationDialog::CalcFilter()
{
   double loLog = log10(mLoFreq);
   double hiLog = log10(mHiFreq);
   double denom = hiLog - loLog;

   double delta = mHiFreq / ((double)(mWindowSize/2.));
   double val0;
   double val1;

   bool lin = mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked();

   if( lin )
   {
      val0 = mLinEnvelope->GetValue(0.0);   //no scaling required - saved as dB
      val1 = mLinEnvelope->GetValue(1.0);
   }
   else
   {
      val0 = mLogEnvelope->GetValue(0.0);   //no scaling required - saved as dB
      val1 = mLogEnvelope->GetValue(1.0);
   }
   mFilterFuncR[0] = val0;
   double freq = delta;

   int i;
   for(i=1; i<=mWindowSize/2; i++)
   {
      double when;
      if( lin )
         when = freq/mHiFreq;
      else
         when = (log10(freq) - loLog)/denom;
      if(when < 0.)
      {
         mFilterFuncR[i] = val0;
      }
      else  if(when > 1.0)
            {
               mFilterFuncR[i] = val1;
            }
            else
            {
               if( lin )
                  mFilterFuncR[i] = mLinEnvelope->GetValue(when);
               else
                  mFilterFuncR[i] = mLogEnvelope->GetValue(when);
            }
      freq += delta;
   }
   mFilterFuncR[mWindowSize/2] = val1;

   mFilterFuncR[0] = (float)(pow(10., mFilterFuncR[0]/20.));
   for(i=1;i<mWindowSize/2;i++)
   {
      mFilterFuncR[i] = (float)(pow(10., mFilterFuncR[i]/20.));
      mFilterFuncR[mWindowSize-i]=mFilterFuncR[i];   //Fill entire array
   }
   mFilterFuncR[i] = (float)(pow(10., mFilterFuncR[i]/20.));   //do last one

   //transfer to time domain to do the padding and windowing
   float *outr = new float[mWindowSize];
   float *outi = new float[mWindowSize];
#ifdef EXPERIMENTAL_USE_REALFFTF
   InverseRealFFT(mWindowSize, mFilterFuncR, NULL, outr); // To time domain
#else
   FFT(mWindowSize,true,mFilterFuncR,NULL,outr,outi);   //To time domain
#endif

   for(i=0;i<=(M-1)/2;i++)
   {   //Windowing - could give a choice, fixed for now - MJS
//      double mult=0.54-0.46*cos(2*M_PI*(i+(M-1)/2.0)/(M-1));   //Hamming
//Blackman
      double mult=0.42-0.5*cos(2*M_PI*(i+(M-1)/2.0)/(M-1))+.08*cos(4*M_PI*(i+(M-1)/2.0)/(M-1));
      outr[i]*=mult;
      if(i!=0){
         outr[mWindowSize-i]*=mult;
      }
   }
   for(;i<=mWindowSize/2;i++)
   {   //Padding
      outr[i]=0;
      outr[mWindowSize-i]=0;
   }
   float *tempr = new float[M];
   for(i=0;i<(M-1)/2;i++)
   {   //shift so that padding on right
      tempr[(M-1)/2+i]=outr[i];
      tempr[i]=outr[mWindowSize-(M-1)/2+i];
   }
   tempr[(M-1)/2+i]=outr[i];

   for(i=0;i<M;i++)
   {   //and copy useful values back
      outr[i]=tempr[i];
   }
   for(i=M;i<mWindowSize;i++)
   {   //rest is padding
      outr[i]=0.;
   }

   //Back to the frequency domain so we can use it
   RealFFT(mWindowSize,outr,mFilterFuncR,mFilterFuncI);

   delete[] outr;
   delete[] outi;
   delete[] tempr;

   return TRUE;
}

//
// Make the passed curve index the active one
//
void EqualizationDialog::setCurve(int currentCurve)
{
   // Set current choice
   Select( currentCurve );

   wxASSERT( currentCurve < (int) mCurves.GetCount() );
   bool changed = false;

   if( mLinFreq->IsChecked() )   // linear freq mode?
   {
      Envelope *env = mLinEnvelope;
      env->Flatten(0.);
      env->SetTrackLen(1.0);

      if( mCurves[currentCurve].points.GetCount() )
      {
         double when, value;
         int i;
         int nCurvePoints = mCurves[currentCurve].points.GetCount();
         for(i=0;i<nCurvePoints;i++)
         {
            when = mCurves[currentCurve].points[i].Freq / mHiFreq;
            value = mCurves[currentCurve].points[i].dB;
            if(when <= 1)
               env->Insert(when, value);
            else
               break;
         }
         if ( i != nCurvePoints) // there are more points at higher freqs
         {
            when = 1.;  // set the RH end to the next highest point
            value = mCurves[currentCurve].points[nCurvePoints-1].dB;
            env->Insert(when, value);
            changed = true;
         }
      }
   }
   else
   {
      Envelope *env = mLogEnvelope;
      env->Flatten(0.);
      env->SetTrackLen(1.0);

      if( mCurves[currentCurve].points.GetCount() )
      {
         double when, value;
         double loLog = log10(20.);
         double hiLog = log10(mHiFreq);
         double denom = hiLog - loLog;
         int i;
         int nCurvePoints = mCurves[currentCurve].points.GetCount();

         for(i=0;i<nCurvePoints;i++)
         {
            if( mCurves[currentCurve].points[i].Freq >= 20)
            {
               when = (log10(mCurves[currentCurve].points[i].Freq) - loLog)/denom;
               value = mCurves[currentCurve].points[i].dB;
               if(when <= 1)
                  env->Insert(when, value);
               else
                  break;
            }
            else
            {  //get the first point as close as we can to the last point requested
               changed = true;
               //double f = mCurves[currentCurve].points[i].Freq;
               //double v = mCurves[currentCurve].points[i].dB;
               mLogEnvelope->Insert(0., mCurves[currentCurve].points[i].dB);
            }
         }
         if ( i != nCurvePoints)
         {
            when = 1.;
            value = mCurves[currentCurve].points[nCurvePoints-1].dB;
            env->Insert(when, value);
            changed = true;
         }
      }
   }
   if(changed) // not all points were loaded so switch to custom
      EnvelopeUpdated();
   mPanel->RecalcRequired = true;
   mPanel->Refresh( false );
}
void EqualizationDialog::setCurve()
{
   setCurve((int) mCurves.GetCount()-1);
}

void EqualizationDialog::setCurve(wxString curveName)
{
   unsigned i = 0;
   for( i = 0; i < mCurves.GetCount(); i++ )
      if( curveName == mCurves[ i ].Name )
         break;
   if( i == mCurves.GetCount())
   {
      wxMessageBox( wxT("Requested curve not found, using 'custom'"), wxT("Curve not found"), wxOK|wxICON_ERROR );
      setCurve((int) mCurves.GetCount()-1);
   }
   else
      setCurve( i );
}

//
// Set new curve selection and manage state of delete button
//
void EqualizationDialog::Select( int curve )
{
   // Set current choice
   mCurve->SetSelection( curve );
   curveName = mCurves[ curve ].Name;

   // If the "custom" curve became active
   if( (unsigned int)curve + 1U == mCurve->GetCount() )
   {
      // Prevent focus from being lost
      if( mDelete->FindFocus() == mDelete )
      {
         mCurve->SetFocus();
      }

      // Not allowed to delete the "custom" curve
      mDelete->Disable();
   }
   else
   {
      // Allow the curve to be deleted
      mDelete->Enable();
   }
}

//
// Capture updated envelope
//
void EqualizationDialog::EnvelopeUpdated()
{
   if( mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked() )
      EnvelopeUpdated(mLinEnvelope, true);
   else
      EnvelopeUpdated(mLogEnvelope, false);
}

void EqualizationDialog::EnvelopeUpdated(Envelope *env, bool lin)
{
   // Allocate and populate point arrays
   int numPoints = env->GetNumberOfPoints();
   double *when = new double[ numPoints ];
   double *value = new double[ numPoints ];
   env->GetPoints( when, value, numPoints );

   // Clear the custom curve
   int curve = mCurves.GetCount()-1;
   mCurves[ curve ].points.Clear();

   if(lin)
   {
      // Copy and convert points
      int point;
      for( point = 0; point < numPoints; point++ )
      {
         double freq = when[ point ] * mHiFreq;
         double db = value[ point ];

         // Add it to the curve
         mCurves[ curve ].points.Add( EQPoint( freq, db ) );
      }
   }
   else
   {
      double loLog = log10( 20. );
      double hiLog = log10( mHiFreq );
      double denom = hiLog - loLog;

      // Copy and convert points
      int point;
      for( point = 0; point < numPoints; point++ )
      {
         double freq = pow( 10., ( ( when[ point ] * denom ) + loLog ));
         double db = value[ point ];

         // Add it to the curve
         mCurves[ curve ].points.Add( EQPoint( freq, db ) );
      }
   }
   // Remember that we've updated the custom curve
   mDirty = true;

   // set 'custom' as the selected curve
   Select( (int) mCurves.GetCount()-1 );

   // Clean up
   delete [] when;
   delete [] value;
}

//
// Process XML tags and handle the ones we recognize
//
bool EqualizationDialog::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
   // May want to add a version strings...
   if( !wxStrcmp( tag, wxT("equalizationeffect") ) )
   {
      return true;
   }

   // Located a new curve
   if( !wxStrcmp(tag, wxT("curve") ) )
   {
      // Process the attributes
      while( *attrs )
      {
         // Cache attr/value and bump to next
         const wxChar *attr = *attrs++;
         const wxChar *value = *attrs++;

         // Create a new curve and name it
         if( !wxStrcmp( attr, wxT("name") ) )
         {
            const wxString strValue = value;
            if (!XMLValueChecker::IsGoodString(strValue))
               return false;
            mCurves.Add( EQCurve( strValue ) );
         }
      }

      // Tell caller it was processed
      return true;
   }

   // Located a new point
   if( !wxStrcmp( tag, wxT("point") ) )
   {
      // Set defaults in case attributes are missing
      double f = 0.0;
      double d = 0.0;

      // Process the attributes
      double dblValue;
      while( *attrs )
      {   // Cache attr/value and bump to next
         const wxChar *attr = *attrs++;
         const wxChar *value = *attrs++;

         const wxString strValue = value;

         // Get the frequency
         if( !wxStrcmp( attr, wxT("f") ) )
         {
            if (!XMLValueChecker::IsGoodString(strValue) ||
                  !Internat::CompatibleToDouble(strValue, &dblValue))
               return false;
            f = dblValue;
         }
         // Get the dB
         else if( !wxStrcmp( attr, wxT("d") ) )
         {
            if (!XMLValueChecker::IsGoodString(strValue) ||
                  !Internat::CompatibleToDouble(strValue, &dblValue))
               return false;
            d = dblValue;
         }
      }

      // Create a new point
      mCurves[ mCurves.GetCount() - 1 ].points.Add( EQPoint( f, d ) );

      // Tell caller it was processed
      return true;
   }

   // Tell caller we didn't understand the tag
   return false;
}

//
// Return handler for recognized tags
//
XMLTagHandler *EqualizationDialog::HandleXMLChild(const wxChar *tag)
{
   if( !wxStrcmp( tag, wxT("equalizationeffect") ) )
   {
      return this;
   }

   if( !wxStrcmp( tag, wxT("curve") ) )
   {
      return this;
   }

   if( !wxStrcmp( tag, wxT("point") ) )
   {
      return this;
   }

   return NULL;
}

//
// Write all of the curves to the XML file
//
void EqualizationDialog::WriteXML(XMLWriter &xmlFile)
{
   // Start our heirarchy
   xmlFile.StartTag( wxT( "equalizationeffect" ) );

   // Write all curves
   int numCurves = mCurves.GetCount();
   int curve;
   for( curve = 0; curve < numCurves; curve++ )
   {
      // Start a new curve
      xmlFile.StartTag( wxT( "curve" ) );
      xmlFile.WriteAttr( wxT( "name" ), mCurves[ curve ].Name );

      // Write all points
      int numPoints = mCurves[ curve ].points.GetCount();
      int point;
      for( point = 0; point < numPoints; point++ )
      {
         // Write new point
         xmlFile.StartTag( wxT( "point" ) );
         xmlFile.WriteAttr( wxT( "f" ), mCurves[ curve ].points[ point ].Freq, 12 );
         xmlFile.WriteAttr( wxT( "d" ), mCurves[ curve ].points[ point ].dB, 12 );
         xmlFile.EndTag( wxT( "point" ) );
      }

      // Terminate curve
      xmlFile.EndTag( wxT( "curve" ) );
   }

   // Terminate our heirarchy
   xmlFile.EndTag( wxT( "equalizationeffect" ) );
}

// WDR: handler implementations for EqualizationDialog

//
// Graphic EQ slider was adjusted
//

void EqualizationDialog::OnSlider(wxCommandEvent &event)
{
   wxSliderBugfix *s = (wxSliderBugfix *)event.GetEventObject();
   for (int i = 0; thirdOct[i] <= mHiFreq; ++i)
   {
      if( i == NUMBER_OF_BANDS )
         break;
      if( s == m_sliders[i])
      {
         int posn = m_sliders[i]->GetValue();
         if( wxGetKeyState(WXK_SHIFT) )
         {
            if( posn > m_sliders_old[i] )
               m_EQVals[i] += (float).1;
            else
               if( posn < m_sliders_old[i] )
                  m_EQVals[i] -= .1f;
         }
         else
            m_EQVals[i] += (posn - m_sliders_old[i]);
         if( m_EQVals[i] > 20. )
            m_EQVals[i] = 20.;
         if( m_EQVals[i] < -20. )
            m_EQVals[i] = -20.;
         int newPosn = (int)m_EQVals[i];
         m_sliders[i]->SetValue( newPosn );
         m_sliders_old[i] = newPosn;
#if wxUSE_TOOLTIPS
         wxString tip;
         if( thirdOct[i] < 1000.)
            tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] );
         else
            tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] );
         s->SetToolTip(tip);
#endif
         break;
      }
   }
   GraphicEQ(mLogEnvelope);
   EnvelopeUpdated();
}

void EqualizationDialog::OnInterp(wxCommandEvent &event)
{
   if(mFaderOrDraw[1]->GetValue())
   {
      GraphicEQ(mLogEnvelope);
      EnvelopeUpdated();
   }
   interp = mInterpChoice->GetSelection();
}

void EqualizationDialog::LayoutEQSliders()
{
   //layout the Graphic EQ sizer here
   wxSize szrGSize = szrG->GetSize();   //total size we have to play with
   wxSize szr2Size = szr2->GetSize();   //size of the dBMax/Min sliders
   wxSizerItem *ruler = szr1->GetItem((size_t)1);
   wxSize rulerSize = ruler->GetSize();   //and the ruler
   wxSizerItem *EQslider = szrG->GetItem((size_t)1);
   wxSize EQsliderSize = EQslider->GetSize();   //size of the sliders

#if defined(__WXMAC__)
   // LL: 2010-01-04 - Don't know why, but on the Mac, the rightmost sliders
   // will wind up off the edge of the window since they get spaced out too
   // much.  Somewhere, there's an extra 2 pixels in slider width that's not
   // being accounted for.  (I guess)
   EQsliderSize.x += 2;
#endif

   int start, w, range, so_far;
   start = szr2Size.x + rulerSize.x + 12;   //inc ruler & mPanel border (4+4 + 4)
   szrG->SetItemMinSize((size_t)0, start - EQsliderSize.x/2, -1);   //set 1st spacer so that 1st slider aligned with ruler
   range = szrGSize.x - EQsliderSize.x/2 - start;
   so_far = start + EQsliderSize.x/2;

   double loLog = log10(mLoFreq);
   double hiLog = log10(mHiFreq);
   double denom = hiLog - loLog;
   for (int i = 1; thirdOct[i] <= mHiFreq; ++i)   //go along the spacers
   {
      if( i == NUMBER_OF_BANDS )
         break;
      float posn = range*(log10(thirdOct[i])-loLog)/denom;   //centre of this slider, from start
      w = start + ((int)(posn+.5)) - EQsliderSize.x/2;   //LH edge of slider, from 0
      w = w - so_far;   //gap needed to put it here
      szrG->SetItemMinSize((size_t)(i*2), w, -1);   //set spacers so that sliders aligned with ruler
      so_far += (w + EQsliderSize.x);
   }

   RefreshRect(wxRect(szrG->GetPosition(),szrGSize));
}

void EqualizationDialog::GraphicEQ(Envelope *env)
{
   double value, dist, span, s;

   env->Flatten(0.);
   env->SetTrackLen(1.0);

   int n = mInterpChoice->GetSelection();
   switch( n )
   {
      case 0:  // B-spline
      {
         int minF = 0;
         for(int i=0; i<NUM_PTS; i++)
         {
            while( (whenSliders[minF] <= whens[i]) & (minF < bandsInUse) )
               minF++;
            minF--;
            if( minF < 0 ) //before first slider
            {
               dist = whens[i] - whenSliders[0];
               span = whenSliders[1] - whenSliders[0];
               s = dist/span;
               if( s < -1.5 )
                  value = 0.;
               else
                  if( s < -.5 )
                     value = m_EQVals[0]*(s + 1.5)*(s + 1.5)/2.;
                  else
                     value = m_EQVals[0]*(.75 - s*s) + m_EQVals[1]*(s + .5)*(s + .5)/2.;
            }
            else
            {
               if( whens[i] > whenSliders[bandsInUse-1] )   //after last fader
               {
                  dist = whens[i] - whenSliders[bandsInUse-1];
                  span = whenSliders[bandsInUse-1] - whenSliders[bandsInUse-2];
                  s = dist/span;
                  if( s > 1.5 )
                     value = 0.;
                  else
                     if( s > .5 )
                        value = m_EQVals[bandsInUse-1]*(s - 1.5)*(s - 1.5)/2.;
                     else
                        value = m_EQVals[bandsInUse-1]*(.75 - s*s) +
                                 m_EQVals[bandsInUse-2]*(s - .5)*(s - .5)/2.;
               }
               else  //normal case
               {
                  dist = whens[i] - whenSliders[minF];
                  span = whenSliders[minF+1] - whenSliders[minF];
                  s = dist/span;
                  if(s < .5 )
                  {
                     value = m_EQVals[minF]*(0.75 - s*s);
                     if( minF+1 < bandsInUse )
                        value += m_EQVals[minF+1]*(s+.5)*(s+.5)/2.;
                     if( minF-1 >= 0 )
                        value += m_EQVals[minF-1]*(s-.5)*(s-.5)/2.;
                  }
                  else
                  {
                     value = m_EQVals[minF]*(s-1.5)*(s-1.5)/2.;
                     if( minF+1 < bandsInUse )
                        value += m_EQVals[minF+1]*(.75-(1.-s)*(1.-s));
                     if( minF+2 < bandsInUse )
                        value += m_EQVals[minF+2]*(s-.5)*(s-.5)/2.;
                  }
               }
            }
            if(whens[i]<=0.)
               env->Move( 0., value );
            env->Insert( whens[i], value );
         }
         env->Move( 1., value );
         break;
      }

      case 1:  // Cosine squared
      {
         int minF = 0;
         for(int i=0; i<NUM_PTS; i++)
         {
            while( (whenSliders[minF] <= whens[i]) & (minF < bandsInUse) )
               minF++;
            minF--;
            if( minF < 0 ) //before first slider
            {
               dist = whenSliders[0] - whens[i];
               span = whenSliders[1] - whenSliders[0];
               if( dist < span )
                  value = m_EQVals[0]*(1. + cos(M_PI*dist/span))/2.;
               else
                  value = 0.;
            }
            else
            {
               if( whens[i] > whenSliders[bandsInUse-1] )   //after last fader
               {
                  span = whenSliders[bandsInUse-1] - whenSliders[bandsInUse-2];
                  dist = whens[i] - whenSliders[bandsInUse-1];
                  if( dist < span )
                     value = m_EQVals[bandsInUse-1]*(1. + cos(M_PI*dist/span))/2.;
                  else
                     value = 0.;
               }
               else  //normal case
               {
                  span = whenSliders[minF+1] - whenSliders[minF];
                  dist = whenSliders[minF+1] - whens[i];
                  value = m_EQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. +
                  m_EQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.;
               }
            }
            if(whens[i]<=0.)
               env->Move( 0., value );
            env->Insert( whens[i], value );
         }
         env->Move( 1., value );
         break;
      }

      case 2:  // Cubic Spline
      {
         double y2[NUMBER_OF_BANDS+1];
         m_EQVals[bandsInUse] = m_EQVals[bandsInUse-1];
         spline(whenSliders, m_EQVals, bandsInUse+1, y2);
         for(double xf=0; xf<1.; xf+=1./NUM_PTS)
         {
            env->Insert(xf, splint(whenSliders, m_EQVals, bandsInUse+1, y2, xf));
         }
         break;
      }
   }

   mPanel->RecalcRequired = true;
   mPanel->Refresh( false );
}

void EqualizationDialog::spline(double x[], double y[], int n, double y2[])
{
   int i;
   double p, sig, *u = new double[n];

   y2[0] = 0.;  //
   u[0] = 0.;   //'natural' boundary conditions
   for(i=1;i<n-1;i++)
   {
      sig = ( x[i] - x[i-1] ) / ( x[i+1] - x[i-1] );
      p = sig * y2[i-1] + 2.;
      y2[i] = (sig - 1.)/p;
      u[i] = ( y[i+1] - y[i] ) / ( x[i+1] - x[i] ) - ( y[i] - y[i-1] ) / ( x[i] - x[i-1] );
      u[i] = (6.*u[i]/( x[i+1] - x[i-1] ) - sig * u[i-1]) / p;
   }
   y2[n-1] = 0.;
   for(i=n-2;i>=0;i--)
      y2[i] = y2[i]*y2[i+1] + u[i];

   delete [] u;
}

double EqualizationDialog::splint(double x[], double y[], int n, double y2[], double xr)
{
   double a, b, h;
   static double xlast = 0.;   // remember last x value requested
   static int k = 0;           // and which interval we were in

   if( xr < xlast )
      k = 0;                   // gone back to start, (or somewhere to the left)
   xlast = xr;
   while( (x[k] <= xr) && (k < n-1) )
      k++;
   k--;
   h = x[k+1] - x[k];
   a = ( x[k+1] - xr )/h;
   b = (xr - x[k])/h;
   return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.);
}

void EqualizationDialog::OnDrawRadio(wxCommandEvent &evt)
{
   int numPoints = mLogEnvelope->GetNumberOfPoints();
   double *when = new double[ numPoints ];
   double *value = new double[ numPoints ];
   double deltadB = 0.1;
   double dx, dy, dx1, dy1, err;

   mLogEnvelope->GetPoints( when, value, numPoints );

   // set 'custom' as the selected curve
   EnvelopeUpdated();

   bool flag = true;
   while (flag)
   {
      flag = false;
      int numDeleted = 0;
      mLogEnvelope->GetPoints( when, value, numPoints );
      for(int j=0;j<numPoints-2;j++)
      {
         dx = when[j+2+numDeleted] - when[j+numDeleted];
         dy = value[j+2+numDeleted] - value[j+numDeleted];
         dx1 = when[j+numDeleted+1] - when[j+numDeleted];
         dy1 = dy * dx1 / dx;
         err = fabs(value[j+numDeleted+1] - (value[j+numDeleted] + dy1));
         if( err < deltadB )
         {   // within < deltadB dB?
            mLogEnvelope->Delete(j+1);
            numPoints--;
            numDeleted++;
            flag = true;
         }
      }
   }
   delete [] when;
   delete [] value;

   if(mLinFreq->IsChecked())
   {
      EnvLogToLin();
      mPanel->mEnvelope = mLinEnvelope;
      freqRuler->ruler.SetLog(false);
      freqRuler->ruler.SetRange(0, mHiFreq);
   }

   szrV->Show(szrG,false);
   szrH->Show(szrI,false);
   szrH->Show(szrL,true);
   Layout();
   mPanel->RecalcRequired = true;   // it may have changed slightly due to the deletion of points
   mPanel->Refresh( false );
   drawMode = true;
}

void EqualizationDialog::OnSliderRadio(wxCommandEvent &evt)
{
   UpdateGraphic();
}

void EqualizationDialog::UpdateGraphic()
{
   double loLog = log10(mLoFreq);
   double hiLog = log10(mHiFreq);
   double denom = hiLog - loLog;

   if(mLinFreq->IsChecked())  //going from lin to log freq scale
   {  // add some extra points to the linear envelope for the graphic to follow
      double step = pow(2., 1./12.);   // twelve steps per octave
      double when,value;
      for(double freq=10.; freq<mHiFreq; freq*=step)
      {
         when = freq/mHiFreq;
         value = mLinEnvelope->GetValue(when);
         mLinEnvelope->Insert(when, value);
      }

      EnvLinToLog();
      mPanel->mEnvelope = mLogEnvelope;
      freqRuler->ruler.SetLog(true);
      freqRuler->ruler.SetRange(mLoFreq, mHiFreq);
   }

   bandsInUse = 0;
   while(thirdOct[bandsInUse] <= mHiFreq) {
      bandsInUse++;
      if(bandsInUse == NUMBER_OF_BANDS)
         break;
   }

   for (int i = 0; i<bandsInUse; ++i)
   {
      if( thirdOct[i] == mLoFreq )
         whenSliders[i] = 0.;
      else
         whenSliders[i] = (log10(thirdOct[i])-loLog)/denom;
      m_EQVals[i] = mLogEnvelope->GetValue(whenSliders[i]);    //set initial values of sliders
      if( m_EQVals[i] > 20.)
         m_EQVals[i] = 20.;
      if( m_EQVals[i] < -20.)
         m_EQVals[i] = -20.;
   }
   ErrMin();                  //move sliders to minimise error
   for (int i = 0; i<bandsInUse; ++i)
   {
      m_sliders[i]->SetValue(lrint(m_EQVals[i])); //actually set slider positions
      m_sliders_old[i] = m_sliders[i]->GetValue();
#if wxUSE_TOOLTIPS
      wxString tip;
      if( thirdOct[i] < 1000.)
         tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] );
      else
         tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] );
      m_sliders[i]->SetToolTip(tip);
#endif
   }
   szrV->Show(szrG,true);  // eq sliders
   szrH->Show(szrI,true);  // interpolation choice
   szrH->Show(szrL,false); // linear freq checkbox
   Layout();            // Make all sizers get resized first
   LayoutEQSliders();   // Then layout sliders
   Layout();            // And layout again to resize dialog

   wxSize wsz = GetSize();
   wxSize ssz = szrV->GetSize();
   if (ssz.x > wsz.x || ssz.y > wsz.y)
   {
      Fit();
   }
   GraphicEQ(mLogEnvelope);
   drawMode = false;
}

void EqualizationDialog::OnLinFreq(wxCommandEvent &evt)
{
   if(mLinFreq->IsChecked())  //going from log to lin freq scale
   {
      freqRuler->ruler.SetLog(false);
      freqRuler->ruler.SetRange(0, mHiFreq);
      EnvLogToLin();
      mPanel->mEnvelope = mLinEnvelope;
      linCheck = true;
   }
   else  //going from lin to log freq scale
   {
      freqRuler->ruler.SetLog(true);
      freqRuler->ruler.SetRange(mLoFreq, mHiFreq);
      EnvLinToLog();
      mPanel->mEnvelope = mLogEnvelope;
      linCheck = false;
   }
   mPanel->Recalc();
   freqRuler->Refresh(false);
   mPanel->Refresh(false);
}

void EqualizationDialog::EnvLogToLin(void)
{
   int numPoints = mLogEnvelope->GetNumberOfPoints();
   if( numPoints == 0 )
   {
      return;
   }

   double *when = new double[ numPoints ];
   double *value = new double[ numPoints ];

   mLinEnvelope->Flatten(0.);
   mLinEnvelope->SetTrackLen(1.0);
   mLogEnvelope->GetPoints( when, value, numPoints );
   mLinEnvelope->Move(0., value[0]);
   double loLog = log10(20.);
   double hiLog = log10(mHiFreq);
   double denom = hiLog - loLog;

   for( int i=0; i < numPoints; i++)
      mLinEnvelope->Insert(pow( 10., ((when[i] * denom) + loLog))/mHiFreq , value[i]);
   mLinEnvelope->Move(1., value[numPoints-1]);

   delete [] when;
   delete [] value;
}

void EqualizationDialog::EnvLinToLog(void)
{
   int numPoints = mLinEnvelope->GetNumberOfPoints();
   if( numPoints == 0 )
   {
      return;
   }
   
   double *when = new double[ numPoints ];
   double *value = new double[ numPoints ];

   mLogEnvelope->Flatten(0.);
   mLogEnvelope->SetTrackLen(1.0);
   mLinEnvelope->GetPoints( when, value, numPoints );
   mLogEnvelope->Move(0., value[0]);
   double loLog = log10(20.);
   double hiLog = log10(mHiFreq);
   double denom = hiLog - loLog;
   bool changed = false;

   for( int i=0; i < numPoints; i++)
   {
      if( when[i]*mHiFreq >= 20 )
      {
         mLogEnvelope->Insert((log10(when[i]*mHiFreq)-loLog)/denom , value[i]);
      }
      else
      {  //get the first point as close as we can to the last point requested
         changed = true;
         double v = value[i];
         mLogEnvelope->Insert(0., v);
      }
   }
   mLogEnvelope->Move(1., value[numPoints-1]);

   delete [] when;
   delete [] value;

   if(changed)
      EnvelopeUpdated(mLogEnvelope, false);
}

void EqualizationDialog::ErrMin(void)
{
   double vals[NUM_PTS];
   int i;
   double error = 0.0;
   double oldError = 0.0;
   double m_EQValsOld = 0.0;
   double correction = 1.6;
   bool flag;
   int j=0;
   Envelope *testEnvelope;
   testEnvelope = new Envelope();
   testEnvelope->SetInterpolateDB(false);
   testEnvelope->Mirror(false);
   testEnvelope->Flatten(0.);
   testEnvelope->SetTrackLen(1.0);
   testEnvelope->CopyFrom(mLogEnvelope, 0.0, 1.0);

   for(i=0; i < NUM_PTS; i++)
      vals[i] = testEnvelope->GetValue(whens[i]);

   //   Do error minimisation
   error = 0.;
   GraphicEQ(testEnvelope);
   for(i=0; i < NUM_PTS; i++)   //calc initial error
   {
      double err = vals[i] - testEnvelope->GetValue(whens[i]);
      error += err*err;
   }
   oldError = error;
   while( j < bandsInUse*12 )  //loop over the sliders a number of times
   {
      i = j%bandsInUse;       //use this slider
      if( (j > 0) & (i == 0) )   // if we've come back to the first slider again...
      {
         if( correction > 0 )
            correction = -correction;     //go down
         else
            correction = -correction/2.;  //go up half as much
      }
      flag = true;   // check if we've hit the slider limit
      do
      {
         oldError = error;
         m_EQValsOld = m_EQVals[i];
         m_EQVals[i] += correction;    //move fader value
         if( m_EQVals[i] > 20. )
         {
            m_EQVals[i] = 20.;
            flag = false;
         }
         if( m_EQVals[i] < -20. )
         {
            m_EQVals[i] = -20.;
            flag = false;
         }
         GraphicEQ(testEnvelope);         //calculate envelope
         error = 0.;
         for(int k=0; k < NUM_PTS; k++)  //calculate error
         {
            double err = vals[k] - testEnvelope->GetValue(whens[k]);
            error += err*err;
         }
      }
      while( (error < oldError) & flag );
      if( error > oldError )
      {
         m_EQVals[i] = m_EQValsOld;   //last one didn't work
         error = oldError;
      }
      else
         oldError = error;
      if( error < .0025 * bandsInUse)
         break;   // close enuff
      j++;  //try next slider
   }
   if( error > .0025 * bandsInUse ) // not within 0.05dB on each slider, on average
   {
      Select( (int) mCurves.GetCount()-1 );
      EnvelopeUpdated(testEnvelope, false);
   }
   delete testEnvelope;
   testEnvelope = NULL;
}

void EqualizationDialog::OnSliderM(wxCommandEvent &event)
{
   TransferDataFromWindow();
   mPanel->RecalcRequired = true;
}

void EqualizationDialog::OnSliderDBMIN(wxCommandEvent &event)
{
   TransferDataFromWindow();
}

void EqualizationDialog::OnSliderDBMAX(wxCommandEvent &event)
{
   TransferDataFromWindow();
}

//
// New curve was selected
//
void EqualizationDialog::OnCurve(wxCommandEvent &event)
{
   // Select new curve
   #if wxCHECK_VERSION(2, 6, 2) && !defined(__WXX11__)
   setCurve( mCurve->GetCurrentSelection() );
   #else
   setCurve( mCurve->GetSelection() );
   #endif
   if( !drawMode )
      UpdateGraphic();
}

//
// User wants to save a new curve
//
void EqualizationDialog::OnSaveAs(wxCommandEvent &event)
{
   SaveAs();
}

bool EqualizationDialog::SaveAs()
{
   wxString name;
   int numCurves = mCurves.GetCount();
   int curve;
   bool overWrite = false;

   // build the dialog
   wxTextEntryDialog dlg( this,
                          _("Enter the desired name of the curve"),
                          _("Save As...") );

   // Setup list of characters that aren't allowed
   wxArrayString exclude;
   exclude.Add( wxT("<") );
   exclude.Add( wxT(">") );
   exclude.Add( wxT("'") );
   exclude.Add( wxT("\"") );

   // And tell the validator about them
   dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
   wxTextValidator *tv = dlg.GetTextValidator();
   tv->SetExcludes( exclude );

   // Prompt the user until a valid name is enter or cancelled
   while( true )
   {   // Show the dialog and bail if the user cancels
      if( dlg.ShowModal() == wxID_CANCEL )
      {
         return false;
      }

      // Extract the name from the dialog
      name = dlg.GetValue();

      // Search list of curves for a duplicate name
      bool bad = false;
      for( curve = 0; curve < numCurves; curve++ )
      {
         if( name == mCurves[ curve ].Name )
         {
            int answer = wxMessageBox( wxT("Overwrite existing curve?"),
                     wxT("Curve exists"), wxYES_NO );
            if (answer == wxNO)
            {
               bad = true;
               break;
            }
            else
            {
               overWrite = true;
               break;
            }
         }
      }
      if( name == wxT("") || name == wxT("custom") )
         bad = true;

      // An unacceptable duplicate wasn't found so break out of loop
      if( !bad )
      {
         break;
      }
   }

   if( overWrite == false)
   {
      // Create a new entry
      mCurves.Add( EQCurve( wxT("custom") ) );

      // Copy over the points
      mCurves[ numCurves ].points = mCurves[ numCurves - 1 ].points;

      // Give the original custom entry the new name
      mCurves[ numCurves - 1 ].Name = name;

      // Recreate the choice control and layout everything again
      CreateChoice();
      Layout();

      // Select the newly name curve
      Select( numCurves - 1 );
   }
   else
   {
      // Overwrite curve from custom
      mCurves[ curve ].points = mCurves[ numCurves - 1 ].points;

      // Select the overwritten curve
      Select( curve );
   }

   // Save the curves to file - enables the user to set up curves without applying them
   SaveCurves();
   return true;
}

//
// User wants to delete a curve
//
void EqualizationDialog::OnDelete(wxCommandEvent &event)
{
   // Get the one to be deleted
   int sel = mCurve->GetSelection();

   // Create the prompt
   wxString quest;
   quest = wxString(_("Delete")) + wxT(" ") + mCurves[ sel ].Name + wxT("?");

   // Ask for confirmation
   int ans = wxMessageBox( quest,
                           _("Confirm Deletion"),
                           wxYES_NO | wxCENTRE,
                           this );

   // Denied, so bail
   if( ans == wxNO )
   {
      return;
   }

   // Remove the curve from the array and choice control
   mCurves.RemoveAt( sel );
   mCurve->Delete( sel );

   // Save the curves to file - enables the user to delete curves without applying anything
   SaveCurves();

   // set 'custom' as the selected curve
   Select( (int) mCurves.GetCount()-1 );

   // Reselect
   setCurve( mCurves.GetCount()-1 );
}

void EqualizationDialog::OnClear(wxCommandEvent &event)
{
   mLogEnvelope->Flatten(0.);
   mLogEnvelope->SetTrackLen(1.0);
   mLinEnvelope->Flatten(0.);
   mLinEnvelope->SetTrackLen(1.0);
   mPanel->RecalcRequired = true;
   mPanel->Refresh(false);
   if( !drawMode )
   {
      for( int i=0; i< bandsInUse; i++)
      {
         m_sliders[i]->SetValue(0);
         m_sliders_old[i] = 0;
         m_EQVals[i] = 0.;
      }
   }
   EnvelopeUpdated();
}

#ifdef EXPERIMENTAL_EQ_INVERT
void EqualizationDialog::OnInvert(wxCommandEvent &event) // Inverts any curve
{
   if(!drawMode)   // Graphic (Slider) mode. Invert the sliders.
   {
      for (int i = 0; thirdOct[i] <= mHiFreq; ++i)
      {
         if( i == NUMBER_OF_BANDS )
            break;
         m_EQVals[i] = -m_EQVals[i];
         int newPosn = (int)m_EQVals[i];
         m_sliders[i]->SetValue( newPosn );
         m_sliders_old[i] = newPosn;
   #if wxUSE_TOOLTIPS
         wxString tip;
         if( thirdOct[i] < 1000.)
            tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] );
         else
            tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] );
         m_sliders[i]->SetToolTip(tip);
   #endif
      }
      GraphicEQ(mLogEnvelope);
   }
   else  // Draw mode.  Invert the points.
   {
      bool lin;   // refers to the 'log' or 'lin' of the frequency scale, not the amplitude
      int numPoints; // number of points in the curve/envelope

      // determine if log or lin curve is the current one
      // and find out how many points are in the curve
      if(mLinFreq->IsChecked())  // lin freq scale and so envelope
      {
         lin = true;
         numPoints = mLinEnvelope->GetNumberOfPoints();
      }
      else
      {
         lin = false;
         numPoints = mLogEnvelope->GetNumberOfPoints();
      }

      if( numPoints == 0 )
         return;

      double *when = new double[ numPoints ];
      double *value = new double[ numPoints ];

      if(lin)
         mLinEnvelope->GetPoints( when, value, numPoints );
      else
         mLogEnvelope->GetPoints( when, value, numPoints );

      // invert the curve
      for( int i=0; i < numPoints; i++)
      {
         if(lin)
            mLinEnvelope->Move(when[i] , -value[i]);
         else
            mLogEnvelope->Move(when[i] , -value[i]);
      }

      // copy it back to the other one (just in case)
      if(lin)
         EnvLinToLog();
      else
         EnvLogToLin();
   }

   // and update the display etc
   mPanel->Recalc();
   mPanel->Refresh(false);
   EnvelopeUpdated();
}
#endif

void EqualizationDialog::OnErase(wxEraseEvent &event)
{
   // Ignore it
}

void EqualizationDialog::OnPaint(wxPaintEvent &event)
{
   wxPaintDC dc(this);

#if defined(__WXGTK__)
   dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)));
#endif

   dc.Clear();
}

void EqualizationDialog::OnSize(wxSizeEvent &event)
{
   Layout();

   if (mFaderOrDraw[1]->GetValue())
   {
      LayoutEQSliders();
   }

   event.Skip();
}

void EqualizationDialog::OnPreview(wxCommandEvent &event)
{
   TransferDataFromWindow();
   m_pEffect->Preview();
   mPanel->RecalcRequired = true;
   //redraw the window.
   Refresh();
   Update();
   //v Restore previous values?
}

void EqualizationDialog::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
{
   if( mGridOnOff->IsChecked() )
      drawGrid = true;
   else
      drawGrid = false;
   mPanel->Refresh(false);
}

void EqualizationDialog::RevertCustom()
{
   EQCurve &realCustom = mCurves[mCurves.GetCount()-1];
   wxASSERT(realCustom.Name.IsSameAs(wxT("custom")));
   realCustom.points = mCustomBackup.points;
}

void EqualizationDialog::Finish(bool ok)
{
   if(mLogEnvelope)
     delete mLogEnvelope;
   mLogEnvelope = NULL;
   if(mLinEnvelope)
     delete mLinEnvelope;
   mLinEnvelope = NULL;
   mPanel = NULL;

   EndModal(ok);
}

void EqualizationDialog::OnCancel(wxCommandEvent &event)
{
   if (mDisallowCustom)
      RevertCustom();

   Finish(false);
}

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

   if( Validate() )
   {
      // Update custom curve (so it's there for next time)
      //(done in a hurry, may not be the neatest -MJS)
      if( !mDisallowCustom)
      {
         if ( mDirty && drawMode )
         {
            int i, j;
            int numPoints = mLogEnvelope->GetNumberOfPoints();
            double *when = new double[ numPoints ];
            double *value = new double[ numPoints ];
            mLogEnvelope->GetPoints( when, value, numPoints );
            for(i=0,j=0;j<numPoints-2;i++,j++)
            {
               if( (value[i]<value[i+1]+.05) && (value[i]>value[i+1]-.05) &&
                   (value[i+1]<value[i+2]+.05) && (value[i+1]>value[i+2]-.05) )
               {   // within < 0.05 dB?
                  mLogEnvelope->Delete(j+1);
                  numPoints--;
                  j--;
               }
            }
            delete [] when;
            delete [] value;
            Select( (int) mCurves.GetCount()-1 );
         }
         SaveCurves();
      }
      Finish(true);
   }
   else
   {
      event.Skip(false);
   }
}

#if wxUSE_ACCESSIBILITY

SliderAx::SliderAx( wxWindow * window, wxString fmt ):
   wxWindowAccessible( window )
{
   mParent = window;
   mFmt = fmt;
}

SliderAx::~SliderAx()
{
}

// Retrieves the address of an IDispatch interface for the specified child.
// All objects must support this property.
wxAccStatus SliderAx::GetChild( int childId, wxAccessible** child )
{
   if( childId == wxACC_SELF )
   {
      *child = this;
   }
   else
   {
      *child = NULL;
   }

   return wxACC_OK;
}

// Gets the number of children.
wxAccStatus SliderAx::GetChildCount(int* childCount)
{
   *childCount = 3;

   return wxACC_OK;
}

// Gets the default action for this object (0) or > 0 (the action for a child).
// Return wxACC_OK even if there is no action. actionName is the action, or the empty
// string if there is no action.
// The retrieved string describes the action that is performed on an object,
// not what the object does as a result. For example, a toolbar button that prints
// a document has a default action of "Press" rather than "Prints the current document."
wxAccStatus SliderAx::GetDefaultAction( int childId, wxString *actionName )
{
   actionName->Clear();

   return wxACC_OK;
}

// Returns the description for this object or a child.
wxAccStatus SliderAx::GetDescription( int childId, wxString *description )
{
   description->Clear();

   return wxACC_OK;
}

// Gets the window with the keyboard focus.
// If childId is 0 and child is NULL, no object in
// this subhierarchy has the focus.
// If this object has the focus, child should be 'this'.
wxAccStatus SliderAx::GetFocus(int* childId, wxAccessible** child)
{
   *childId = 0;
   *child = this;

   return wxACC_OK;
}

// Returns help text for this object or a child, similar to tooltip text.
wxAccStatus SliderAx::GetHelpText( int childId, wxString *helpText )
{
   helpText->Clear();

   return wxACC_OK;
}

// Returns the keyboard shortcut for this object or child.
// Return e.g. ALT+K
wxAccStatus SliderAx::GetKeyboardShortcut( int childId, wxString *shortcut )
{
   shortcut->Clear();

   return wxACC_OK;
}

// Returns the rectangle for this object (id = 0) or a child element (id > 0).
// rect is in screen coordinates.
wxAccStatus SliderAx::GetLocation( wxRect& rect, int elementId )
{
   wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix );

   rect = s->GetRect();
   rect.SetPosition( s->GetParent()->ClientToScreen( rect.GetPosition() ) );

   return wxACC_OK;
}

// Gets the name of the specified object.
wxAccStatus SliderAx::GetName(int childId, wxString* name)
{
   wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix );

   *name = s->GetName();

   return wxACC_OK;
}

// Returns a role constant.
wxAccStatus SliderAx::GetRole(int childId, wxAccRole* role)
{
   switch( childId )
   {
      case 0:
         *role = wxROLE_SYSTEM_SLIDER;
      break;

      case 1:
      case 3:
         *role = wxROLE_SYSTEM_PUSHBUTTON;
      break;

      case 2:
         *role = wxROLE_SYSTEM_INDICATOR;
      break;
   }

   return wxACC_OK;
}

// Gets a variant representing the selected children
// of this object.
// Acceptable values:
// - a null variant (IsNull() returns TRUE)
// - a list variant (GetType() == wxT("list"))
// - an integer representing the selected child element,
//   or 0 if this object is selected (GetType() == wxT("long"))
// - a "void*" pointer to a wxAccessible child object
wxAccStatus SliderAx::GetSelections( wxVariant *selections )
{
   return wxACC_NOT_IMPLEMENTED;
}

// Returns a state constant.
wxAccStatus SliderAx::GetState(int childId, long* state)
{
   wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix );

   switch( childId )
   {
      case 0:
         *state = wxACC_STATE_SYSTEM_FOCUSABLE;
      break;

      case 1:
         if( s->GetValue() == s->GetMin() )
         {
            *state = wxACC_STATE_SYSTEM_INVISIBLE;
         }
      break;

      case 3:
         if( s->GetValue() == s->GetMax() )
         {
            *state = wxACC_STATE_SYSTEM_INVISIBLE;
         }
      break;
   }

   // Do not use mSliderIsFocused is not set until after this method
   // is called.
   *state |= ( s == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0 );

   return wxACC_OK;
}

// Returns a localized string representing the value for the object
// or child.
wxAccStatus SliderAx::GetValue(int childId, wxString* strValue)
{
   wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix );

   if( childId == 0 )
   {
      strValue->Printf( mFmt, s->GetValue() );

      return wxACC_OK;
   }

   return wxACC_NOT_SUPPORTED;
}

#endif

// Indentation settings for Vim and Emacs and unique identifier for Arch, a
// version control system. Please do not modify past this point.
//
// Local Variables:
// c-basic-offset: 3
// indent-tabs-mode: nil
// End:
//
// vim: et sts=3 sw=3
// arch-tag: 65b35bfa-632c-46fe-9170-840a158b3c97

