text: /**********************************************************************
  
     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.
  
     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 "../Experimental.h"
  #include "Equalization.h"
  #include "../AColor.h"
  #include "../ShuttleGui.h"
  #include "../PlatformCompatibility.h"
  #include "../FileNames.h"
  #include "../Envelope.h"
  #include "../widgets/LinkingHtmlWindow.h"
  #include "../widgets/ErrorDialog.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/button.h>  // not really needed here
  #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>
  
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
  #include "Equalization48x.h"
  #endif
  
  #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.,
  };
  
  // 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__
  
  #define NUM_INTERP_CHOICES 3
  static wxString interpChoiceStrings[NUM_INTERP_CHOICES];
  
  void EffectEqualization::ReadPrefs()
  {
  
     gPrefs->Read(wxT("/Effects/Equalization/FilterLength"),
        &mM, 4001);
     if ((mM < 21) || (mM > 8191)) {  // corrupted Prefs?
        mM = 4001;  //default
        gPrefs->Write(wxT("/Effects/Equalization/FilterLength"), mM);
     }
     gPrefs->Read(wxT("/Effects/Equalization/CurveName"), &mCurveName, wxT("unnamed"));
     gPrefs->Read(wxT("/Effects/Equalization/Lin"), &mLin, false);
     gPrefs->Read(wxT("/Effects/Equalization/dBMin"), &mdBMin, -30.0);
     if ((mdBMin < -120) || (mdBMin > -10)) {  // corrupted Prefs?
        mdBMin = -30;  //default
        gPrefs->Write(wxT("/Effects/Equalization/FilterLength"), mdBMin);
     }
     gPrefs->Read(wxT("/Effects/Equalization/dBMax"), &mdBMax, 30.);
     if ((mdBMax < 0) || (mdBMax > 60)) {  // corrupted Prefs?
        mdBMax = 30;  //default
        gPrefs->Write(wxT("/Effects/Equalization/FilterLength"), mdBMax);
     }
     gPrefs->Read(wxT("/Effects/Equalization/DrawMode"), &mDrawMode, true);
     gPrefs->Read(wxT("/Effects/Equalization/Interp"), &mInterp, 0);
     gPrefs->Read(wxT("/Effects/Equalization/DrawGrid"), &mDrawGrid, true);
  
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
     bool useSSE;
     gPrefs->Read(wxT("SSE/GUI"), &useSSE);
     if(useSSE && !mEffectEqualization48x)
        mEffectEqualization48x=new EffectEqualization48x;
     else
        if(!useSSE && mEffectEqualization48x) {
           delete mEffectEqualization48x;
           mEffectEqualization48x=NULL;
        }
     mBench=false;
  #endif
     gPrefs->Flush();
  }
  
  EffectEqualization::EffectEqualization()
  {
     hFFT = InitializeFFT(windowSize);
     mFFTBuffer = new float[windowSize];
     mFilterFuncR = new float[windowSize];
     mFilterFuncI = new float[windowSize];
  
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
     mEffectEqualization48x=NULL;
  #endif
  
     ReadPrefs();
  
     mPrompting = false;
  
     /* i18n-hint: Technical term for a kind of curve.*/
     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;
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
     if(mEffectEqualization48x)
        delete mEffectEqualization48x;
  #endif
  }
  
  bool EffectEqualization::Init()
  {
     int selcount = 0;
     double rate = 0.0;
     TrackListIterator iter(GetActiveProject()->GetTracks());
     Track *t = iter.First();
     while (t) {
        if (t->GetSelected() && t->GetKind() == Track::Wave) {
           WaveTrack *track = (WaveTrack *)t;
           if (selcount==0) {
              rate = track->GetRate();
           }
           else {
              if (track->GetRate() != rate) {
                 wxMessageBox(_("To apply Equalization, all selected tracks must have the same sample rate."));
                 return(false);
              }
           }
           selcount++;
        }
        t = iter.Next();
     }
     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("/Effects/Equalization/FilterLength"),mM);
        gPrefs->Write(wxT("/Effects/Equalization/CurveName"),mCurveName);
        gPrefs->Write(wxT("/Effects/Equalization/Lin"),mLin);
        gPrefs->Write(wxT("/Effects/Equalization/dBMin"),mdBMin);
        gPrefs->Write(wxT("/Effects/Equalization/dBMax"),mdBMax);
        gPrefs->Write(wxT("/Effects/Equalization/DrawMode"),mDrawMode);
        gPrefs->Write(wxT("/Effects/Equalization/Interp"), mInterp);
        gPrefs->Write(wxT("/Effects/Equalization/DrawGrid"), mDrawGrid);
        gPrefs->Flush();
     }
  
     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()
  {
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
     if(mEffectEqualization48x)
        if(mBench) {
           mBench=false;
           return mEffectEqualization48x->Benchmark(this);
        } else
           return mEffectEqualization48x->Process(this);
  #endif
     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
              bool bResult = t->Paste(clipStartEndTimes[i].first, toClipOutput);
              wxASSERT(bResult); // TO DO: Actually handle this.
              //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 &  WXUNUSED(event))
  {
     Refresh( false );
  }
  
  void EqualizationPanel::OnPaint(wxPaintEvent &  WXUNUSED(event))
  {
     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(PANELBORDER, PANELBORDER);
  
     // 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);
  
     // Draw the grid, if asked for.  Do it now so it's underneath the main plots.
     if( mParent->drawGrid )
     {
        mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height, true, true, PANELBORDER, PANELBORDER);
        mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width, true, true, PANELBORDER, PANELBORDER);
     }
  
     // 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
           // set up for calculating cos using recurrance - faster than calculating it directly each time
           double theta = M_PI*freq/mHiFreq;   //radians, normalized
           double wtemp = sin(0.5 * theta);
           double wpr = -2.0 * wtemp * wtemp;
           double wpi = -1.0 * sin(theta);
           double wr = cos(theta*halfM);
           double wi = sin(theta*halfM);
  
           yF = 0.;
           for(int j=0;j<halfM;j++)
           {
              yF += 2. * mOutr[j] * wr;  // This works for me, compared to the previous version.  Compare wr to cos(theta*(halfM-j)).  Works for me.  Keep everything as doubles though.
              // do recurrance
              wr = (wtemp = wr) * wpr - wi * wpi + wr;
              wi = wi * wpr + wtemp * wpi + wi;
           }
           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-1, false, dBMin, dBMax);
  
     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))
     {
        mParent->EnvelopeUpdated();
        RecalcRequired = true;
        Refresh(false);
     }
  
     if (event.ButtonUp() && HasCapture())
     {
        ReleaseMouse();
     }
  }
  
  void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(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_MANAGE, EqualizationDialog::OnManage )
     EVT_BUTTON( ID_CLEAR, EqualizationDialog::OnClear )
     EVT_BUTTON( ID_INVERT, EqualizationDialog::OnInvert )
  
     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)
  
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
     EVT_RADIOBUTTON(defaultMathRadioID, EqualizationDialog::OnProcessingRadio)
     EVT_RADIOBUTTON(sSERadioID, EqualizationDialog::OnProcessingRadio)
     EVT_RADIOBUTTON(sSEThreadedRadioID, EqualizationDialog::OnProcessingRadio)
     EVT_RADIOBUTTON(aVXRadioID, EqualizationDialog::OnProcessingRadio)
     EVT_RADIOBUTTON(aVXThreadedRadioID, EqualizationDialog::OnProcessingRadio)
     EVT_BUTTON( ID_BENCH, EqualizationDialog::OnBench )
  #endif
  
     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("unnamed"))
  {
     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);
     mLogEnvelope->SetRange(-120.0, 60.0); // MB: this is the highest possible range
  
     mLinEnvelope = new Envelope();
     mLinEnvelope->SetInterpolateDB(false);
     mLinEnvelope->Mirror(false);
     mLinEnvelope->SetRange(-120.0, 60.0); // MB: this is the highest possible range
  
     mLoFreq = loFreq;
     mHiFreq = hiFreq;
  
     mFilterFuncR = filterFuncR;
     mFilterFuncI = filterFuncI;
     mWindowSize = windowSize;
  
     mCurve = NULL;
     mDirty = false;
  
     // Load the EQ curves
     LoadCurves();
     if (mDisallowCustom)
     {
        mCustomBackup.Name = wxT("unnamed");
        EQCurve &realCustom = mCurves[mCurves.GetCount()-1];
        wxASSERT(realCustom.Name.IsSameAs(wxT("unnamed")));
        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 default, then message
  //
  void EqualizationDialog::LoadCurves(wxString fileName, bool append)
  {
     // Construct normal 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.
     // MJS:  I don't know what the above means, or if I have broken it.
     wxFileName fn;
     if(fileName == wxT(""))
        fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
     else
        fn = wxFileName(fileName); // user is loading a specific set of curves
  
     // If requested file doesn't exist...
     if( !fn.FileExists() )
     {
        // look in data dir first, in case the user has their own defaults (maybe downloaded ones)
        fn = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
        if( !fn.FileExists() )
        {  // Default file not found in the data dir.  Fall back to Resources dir.
           // See http://docs.wxwidgets.org/trunk/classwx_standard_paths.html#5514bf6288ee9f5a0acaf065762ad95d
           static wxString resourcesDir;
           resourcesDir = wxStandardPaths::Get().GetResourcesDir();
           fn = wxFileName( resourcesDir, wxT("EQDefaultCurves.xml") );
        }
        if( !fn.FileExists() )
        {
           wxString errorMessage;
           errorMessage.Printf(_("EQCurves.xml and EQDefaultCurves.xml were not found on your system.\nPlease press 'help' to visit the download page.\n\nSave the curves at %s"), FileNames::DataDir().c_str());
           ShowErrorDialog(this, _("EQCurves.xml and EQDefaultCurves.xml missing"),
              errorMessage, wxT("http://wiki.audacityteam.org/wiki/EQCurvesDownload"), false);
           // Have another go at finding EQCurves.xml in the data dir, in case 'help' helped
           fn = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
           if( !fn.FileExists() )
           {
              mCurves.Add( _("unnamed") );   // we still need a default curve to use
              return;
           }
        }
     }
  
     EQCurve tempCustom(wxT("temp"));
     if( append == false) // Start from scratch
        mCurves.Clear();
     else  // appending so copy and remove 'unnamed', to replace later
     {
        tempCustom.points = mCurves.Last().points;
        mCurves.RemoveAt(mCurves.Count()-1);
     }
  
     // Load the curves
     XMLFileReader reader;
     if( !reader.Parse( this, fn.GetFullPath() ) )
     {
        wxString msg;
        /* i18n-hint: EQ stands for 'Equalization'.*/
        msg.Printf(_("Error Loading EQ Curves from file:\n%s\nError message says:\n%s"), fn.GetFullPath().c_str(), reader.GetErrorStr().c_str());
        // Inform user of load failure
        wxMessageBox( msg,
           _("Error Loading EQ Curves"),
           wxOK | wxCENTRE);
        mCurves.Add( _("unnamed") );  // we always need a default curve to use
        return;
     }
     if( mCurves.Last().Name != _("unnamed") )
        mCurves.Add( _("unnamed") );   // we always need a default curve to use
     if( append == true )
     {
        mCurves.Last().points = tempCustom.points;
     }
  
     return;
  }
  
  //
  // Save curves to external file
  //
  void EqualizationDialog::SaveCurves(wxString fileName)
  {
     wxFileName fn;
     if( fileName == wxT(""))
     {
        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.
        fn = wxFileName( 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;
           }
        }
     }
     else
        fn = wxFileName(fileName);
  
     // 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 );
     szrV->AddSpacer(10);
  
     // -------------------------------------------------------------------
     // 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);
     dBRuler->ruler.mbTicksAtExtremes = true;
     int w, h;
     dBRuler->ruler.GetMaxSize(&w, NULL);
     dBRuler->SetSize(wxSize(w, 150));  // height needed for wxGTK
  
     szr4 = new wxBoxSizer( wxVERTICAL );
     szr4->AddSpacer(PANELBORDER); // vertical space for panel border
     szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL );
     szr4->AddSpacer(PANELBORDER); // vertical space for panel border
     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);
     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.mbTicksAtExtremes = true;
     freqRuler->ruler.GetMaxSize(NULL, &h);
     freqRuler->SetMinSize(wxSize(-1, h));
     szr5 = new wxBoxSizer( wxHORIZONTAL );
     szr5->AddSpacer(PANELBORDER); // horizontal space for panel border
     szr5->Add( freqRuler, 1, wxEXPAND|wxALIGN_LEFT);
     szr5->AddSpacer(PANELBORDER); // horizontal space for panel border
     szr1->Add( szr5, 0, wxEXPAND|wxALIGN_LEFT|wxALL );
  
     szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 );
  
     // -------------------------------------------------------------------
     // 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; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i)
     {
        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
  
     // -------------------------------------------------------------------
     // Graphic or curve drawing?
     // -------------------------------------------------------------------
     szrH = new wxBoxSizer( wxHORIZONTAL );
  
     mFaderOrDraw[0] = new wxRadioButton(
        this, drawRadioID, _("&Draw Curves"),
        wxDefaultPosition, wxDefaultSize, wxRB_GROUP );
     mFaderOrDraw[0]->SetName(_("Draw Curves"));
     szrH->Add( mFaderOrDraw[0], 0, wxRIGHT, 10 );
  
     mFaderOrDraw[1] = new wxRadioButton(
        this, sliderRadioID, _("&Graphic EQ"),
        wxDefaultPosition, wxDefaultSize, 0 );
     mFaderOrDraw[1]->SetName(_("Graphic EQ"));
     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, _("Li&near Frequency Scale"));
     mLinFreq->SetName(_("Linear Frequency Scale"));
     szrL->Add( mLinFreq, 0 );
     szrH->Add(szrL);  // either szrI or szrL are visible, not both.
  
     // -------------------------------------------------------------------
     // 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);
     MSlider->SetName(_("Length of Filter"));
     szrH->Add( MSlider, 0, wxEXPAND );
  
     wxString label;
     label.Printf( wxT("%d"), M );
     mMText = new wxStaticText(this, wxID_ANY, label);
     mMText->SetName(label); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
     szrH->Add( mMText, 0 );
  
     // Add the length / graphic / draw grouping
     szrV->Add( szrH, 0, wxALIGN_CENTER | wxALL, 4 );
  
     // -------------------------------------------------------------------
     // 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();
  
     mManage = new wxButton( this, ID_MANAGE, _("S&ave/Manage Curves...") );
     mManage->SetName(_("Save and Manage Curves"));
     szrC->Add( mManage, 0, wxALIGN_CENTRE|wxLEFT, 4 );
  
     btn = new wxButton( this, ID_CLEAR, _("Fla&tten"));
     szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 );
     btn = new wxButton( this, ID_INVERT, _("&Invert"));
     szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 );
     mGridOnOff = new wxCheckBox(this, GridOnOffID, _("G&rids"),
        wxDefaultPosition, wxDefaultSize,
  #if defined(__WXGTK__)
        // Fixes bug #662
        wxALIGN_LEFT);
  #else
        wxALIGN_RIGHT);
  #endif
     mGridOnOff->SetName(_("Grids"));
     szrC->Add( mGridOnOff, 0, wxALIGN_CENTRE | wxALL, 4 );
  
     szrV->Add( szrC, 0, wxALIGN_CENTER | wxALL, 0 );
  
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
     // -------------------------------------------------------------------
     // Processing routine selection
     // -------------------------------------------------------------------
     if(m_pEffect->mEffectEqualization48x) {
        szrM = new wxBoxSizer( wxHORIZONTAL );
        txt = new wxStaticText( this, wxID_ANY, _("&Processing: ") );
        szrM->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 );
  
        mMathProcessingType[0] = new wxRadioButton(
           this, defaultMathRadioID, _("D&efault"),
           wxDefaultPosition, wxDefaultSize, wxRB_GROUP );
        mMathProcessingType[0]->SetName(_("Default"));
        szrM->Add( mMathProcessingType[0], 0, wxRIGHT, 10 );
  
        mMathProcessingType[1] = new wxRadioButton(
           this, sSERadioID, _("&SSE"),
           wxDefaultPosition, wxDefaultSize, 0 );
        mMathProcessingType[1]->SetName(_("SSE"));
        szrM->Add( mMathProcessingType[1], 0, wxRIGHT, 4 );
  
        mMathProcessingType[2] = new wxRadioButton(
           this, sSEThreadedRadioID, _("SSE &Threaded"),
           wxDefaultPosition, wxDefaultSize, 0 );
        mMathProcessingType[2]->SetName(_("SSE"));
        szrM->Add( mMathProcessingType[2], 0, wxRIGHT, 4 );
  
        mMathProcessingType[3] = new wxRadioButton(
           this, aVXRadioID, _("A&VX"),
           wxDefaultPosition, wxDefaultSize, 0 );
        mMathProcessingType[3]->SetName(_("AVX"));
        szrM->Add( mMathProcessingType[3], 0, wxRIGHT, 4 );
  
        mMathProcessingType[4] = new wxRadioButton(
           this, aVXThreadedRadioID, _("AV&X Threaded"),
           wxDefaultPosition, wxDefaultSize, 0 );
        mMathProcessingType[4]->SetName(_("AVX Threaded"));
        szrM->Add( mMathProcessingType[4], 0, wxRIGHT, 4 );
  
        if(!EffectEqualization48x::GetMathCaps()->SSE) {
           mMathProcessingType[1]->Disable();
           mMathProcessingType[2]->Disable();
        }
        if(true) { //!EffectEqualization48x::GetMathCaps()->AVX) { not implemented
           mMathProcessingType[3]->Disable();
           mMathProcessingType[4]->Disable();
        }
        // update the control state
        mMathProcessingType[0]->SetValue(true);
        int mathPath=EffectEqualization48x::GetMathPath();
        if(mathPath&MATH_FUNCTION_SSE) {
           mMathProcessingType[1]->SetValue(true);
           if(mathPath&MATH_FUNCTION_THREADED)
              mMathProcessingType[2]->SetValue(true);
        }
        if(false) { //mathPath&MATH_FUNCTION_AVX) { not implemented
           mMathProcessingType[3]->SetValue(true);
           if(mathPath&MATH_FUNCTION_THREADED)
              mMathProcessingType[4]->SetValue(true);
        }
        btn = new wxButton( this, ID_BENCH, _("&Bench"));
        szrM->Add( btn, 0, wxRIGHT, 4 );
  
        szrV->Add( szrM, 0, wxALIGN_CENTER | wxALL, 0 );
     }
  #endif
  
     // -------------------------------------------------------------------
     // Preview, OK, & Cancel buttons
     // -------------------------------------------------------------------
     szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND);
  
     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;
     mCurve->SetName(_("Select Curve"));
     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 unnamed curve so
     // we offer to save it.
     while (mDisallowCustom && curveName.IsSameAs(wxT("unnamed")))
     {
        wxMessageBox(_("To use this EQ curve in a batch chain, please choose a new name for it.\nChoose the 'Save/Manage Curves...' button and rename the 'unnamed' curve, then use that one."),
           _("EQ Curve needs a different name"),
           wxOK | wxCENTRE,
           this);
        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));
        mMText->SetName(mMText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
  #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
                 {  // we have a point beyond fs/2.  Insert it so that env code can use it.
                    // but just this one, we have no use for the rest
                    env->SetTrackLen(when); // can't Insert if the envelope isn't long enough
                    env->Insert(when, value);
                    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(changed) // not all points were loaded so switch to unnamed
        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( _("Requested curve not found, using 'unnamed'"), _("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 "unnamed" curve became active
     if( (unsigned int)curve + 1U == mCurve->GetCount() )
     {
        // Prevent focus from being lost
        if( mDelete->FindFocus() == mDelete )
        {
           mCurve->SetFocus();
        }
     }
  }
  
  //
  // 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 unnamed 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 unnamed curve
     mDirty = true;
  
     // set 'unnamed' 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;
              // check for a duplicate name and add (n) if there is one
              int n = 0;
              wxString strValueTemp = strValue;
              bool exists;
              do
              {
                 exists = false;
                 for(size_t i=0;i<mCurves.GetCount();i++)
                 {
                    if(n>0)
                       strValueTemp.Printf(wxT("%s (%d)"),strValue.c_str(),n);
                    if(mCurves[i].Name == strValueTemp)
                    {
                       exists = true;
                       break;
                    }
                 }
                 n++;
              }
              while(exists == true);
  
              mCurves.Add( EQCurve( strValueTemp ) );
           }
        }
  
        // 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; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i)
     {
        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 & WXUNUSED(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; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i)   //go along the spacers
     {
        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)
  {
     // JKC: 'value' is for height of curve.
     // The 0.0 initial value would only get used if NUM_PTS were 0.
     double value = 0.0;
     double 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 & WXUNUSED(event))
  {
     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 'unnamed' 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 & WXUNUSED(event))
  {
     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 & WXUNUSED(event))
  {
     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->SetRange(-120.0, 60.0);
     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 & WXUNUSED(event))
  {
     TransferDataFromWindow();
     mPanel->RecalcRequired = true;
  }
  
  void EqualizationDialog::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event))
  {
     TransferDataFromWindow();
  }
  
  void EqualizationDialog::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event))
  {
     TransferDataFromWindow();
  }
  
  //
  // New curve was selected
  //
  void EqualizationDialog::OnCurve(wxCommandEvent & WXUNUSED(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 modify the list in some way
  //
  void EqualizationDialog::OnManage(wxCommandEvent & WXUNUSED(event))
  {
     EditCurvesDialog d(this, mCurve->GetSelection());
     d.ShowModal();
  }
  
  void EqualizationDialog::OnClear(wxCommandEvent & WXUNUSED(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.;
  #if wxUSE_TOOLTIPS
           wxString tip;
           if( thirdOct[i] < 1000.)
              tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], 0. );
           else
              tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., 0. );
           m_sliders[i]->SetToolTip(tip);
  #endif
        }
     }
     EnvelopeUpdated();
  }
  
  void EqualizationDialog::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts any curve
  {
     if(!drawMode)   // Graphic (Slider) mode. Invert the sliders.
     {
        for (int i = 0; (i < NUMBER_OF_BANDS) && (thirdOct[i] <= mHiFreq); ++i)
        {
           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]);
        }
  
        delete [] when;
        delete [] value;
  
        // 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();
  }
  
  void EqualizationDialog::OnErase(wxEraseEvent & WXUNUSED(event))
  {
     // Ignore it
  }
  
  void EqualizationDialog::OnPaint(wxPaintEvent & WXUNUSED(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 & WXUNUSED(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("unnamed")));
     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 & WXUNUSED(event))
  {
     if (mDisallowCustom)
        RevertCustom();
  
     Finish(false);
  }
  
  void EqualizationDialog::OnOk(wxCommandEvent & event)
  {
     TransferDataFromWindow();
  
     if( Validate() )
     {
        // Update unnamed 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);
     }
  }
  
  //----------------------------------------------------------------------------
  // EditCurvesDialog
  //----------------------------------------------------------------------------
  // Note that the 'modified' curve used to be called 'custom' but is now called 'unnamed'
  // Some things that deal with 'unnamed' curves still use, for example, 'mCustomBackup' as variable names.
  /// Constructor
  
  BEGIN_EVENT_TABLE(EditCurvesDialog, wxDialog)
     EVT_BUTTON(UpButtonID, EditCurvesDialog::OnUp)
     EVT_BUTTON(DownButtonID, EditCurvesDialog::OnDown)
     EVT_BUTTON(RenameButtonID, EditCurvesDialog::OnRename)
     EVT_BUTTON(DeleteButtonID, EditCurvesDialog::OnDelete)
     EVT_BUTTON(ImportButtonID, EditCurvesDialog::OnImport)
     EVT_BUTTON(ExportButtonID, EditCurvesDialog::OnExport)
     EVT_BUTTON(LibraryButtonID, EditCurvesDialog::OnLibrary)
     EVT_BUTTON(DefaultsButtonID, EditCurvesDialog::OnDefaults)
     EVT_BUTTON(wxID_OK, EditCurvesDialog::OnOK)
  END_EVENT_TABLE()
  
  EditCurvesDialog::EditCurvesDialog(EqualizationDialog * parent, int position):
  wxDialog(parent, wxID_ANY, _("Manage Curves List"),
           wxDefaultPosition, wxDefaultSize,
           wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
  {
     SetLabel(_("Manage Curves"));         // Provide visual label
     SetName(_("Manage Curves List"));     // Provide audible label
     mParent = parent;
     mPosition = position;
     // make a copy of mParent->mCurves here to muck about with.
     mEditCurves.Clear();
     for (unsigned int i = 0; i < mParent->mCurves.GetCount(); i++)
     {
        mEditCurves.Add(mParent->mCurves[i].Name);
        mEditCurves[i].points = mParent->mCurves[i].points;
     }
  
     Populate();
  }
  
  EditCurvesDialog::~EditCurvesDialog()
  {
  }
  
  /// Creates the dialog and its contents.
  void EditCurvesDialog::Populate()
  {
     //------------------------- Main section --------------------
     ShuttleGui S(this, eIsCreating);
     PopulateOrExchange(S);
     // ----------------------- End of main section --------------
  }
  
  /// Defines the dialog and does data exchange with it.
  void EditCurvesDialog::PopulateOrExchange(ShuttleGui & S)
  {
     S.StartHorizontalLay(wxEXPAND);
     {
        S.StartStatic(_("&Curves"), 1);
        {
           S.SetStyle(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES );
           mList = S.Id(CurvesListID).AddListControlReportMode();
           mList->InsertColumn(0, _("Curve Name"), wxLIST_FORMAT_RIGHT);
        }
        S.EndStatic();
        S.StartVerticalLay(0);
        {
           S.Id(UpButtonID).AddButton(_("Move &Up"), wxALIGN_LEFT);
           S.Id(DownButtonID).AddButton(_("Move &Down"), wxALIGN_LEFT);
           S.Id(RenameButtonID).AddButton(_("&Rename..."), wxALIGN_LEFT);
           S.Id(DeleteButtonID).AddButton(_("D&elete..."), wxALIGN_LEFT);
           S.Id(ImportButtonID).AddButton(_("I&mport..."), wxALIGN_LEFT);
           S.Id(ExportButtonID).AddButton(_("E&xport..."), wxALIGN_LEFT);
           S.Id(LibraryButtonID).AddButton(_("&Get More..."), wxALIGN_LEFT);
           S.Id(DefaultsButtonID).AddButton(_("De&faults"), wxALIGN_LEFT);
        }
        S.EndVerticalLay();
     }
     S.EndHorizontalLay();
     S.AddStandardButtons();
     S.StartStatic(_("Help"));
     S.AddConstTextBox(wxT(""), _("Rename 'unnamed' to save a new entry.\n'OK' saves all changes, 'Cancel' doesn't."));
     S.EndStatic();
     PopulateList(mPosition);
     Fit();
  
     return;
  }
  
  void EditCurvesDialog::PopulateList(int position)
  {
     mList->DeleteAllItems();
     for (unsigned int i = 0; i < mEditCurves.GetCount(); i++)
        mList->InsertItem(i, mEditCurves[i].Name);
     mList->SetColumnWidth(0, wxLIST_AUTOSIZE);
     int curvesWidth = mList->GetColumnWidth(0);
     mList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
     int headerWidth = mList->GetColumnWidth(0);
     mList->SetColumnWidth(0, wxMax(headerWidth, curvesWidth));
     // use 'position' to set focus
     mList->EnsureVisible(position);
     mList->SetItemState(position, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
  }
  
  void EditCurvesDialog::OnUp(wxCommandEvent & WXUNUSED(event))
  {
     long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     if ( item == -1 )
        return;  // no items selected
     if( item == 0 )
        item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); // top item selected, can't move up
     int state;
     while( item != -1 )
     {
        if ( item == mList->GetItemCount()-1)
        {  // 'unnamed' always stays at the bottom
           wxMessageBox(_("'unnamed' always stays at the bottom of the list"), _("'unnamed' is special"));   // these could get tedious!
           return;
        }
        state = mList->GetItemState(item-1, wxLIST_STATE_SELECTED);
        if ( state != wxLIST_STATE_SELECTED )
        { // swap this with one above but only if it isn't selected
           EQCurve temp(wxT("temp"));
           temp.Name = mEditCurves[item].Name;
           temp.points = mEditCurves[item].points;
           mEditCurves[item].Name = mEditCurves[item-1].Name;
           mEditCurves[item].points = mEditCurves[item-1].points;
           mEditCurves[item-1].Name = temp.Name;
           mEditCurves[item-1].points = temp.points;
           wxString sTemp = mList->GetItemText(item);
           mList->SetItem(item, 0, mList->GetItemText(item-1));
           mList->SetItem(item-1, 0, sTemp);
           mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
           mList->SetItemState(item-1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
        }
        item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     }
  }
  
  void EditCurvesDialog::OnDown(wxCommandEvent & WXUNUSED(event))
  {  // looks harder than OnUp as we need to seek backwards up the list, hence GetPreviousItem
     long item = GetPreviousItem(mList->GetItemCount());
     if( item == -1 )
        return;  // nothing selected
     int state;
     while( item != -1 )
     {
        if( (item != mList->GetItemCount()-1) && (item != mList->GetItemCount()-2) )
        {  // can't move 'unnamed' down, or the one above it
           state = mList->GetItemState(item+1, wxLIST_STATE_SELECTED);
           if ( state != wxLIST_STATE_SELECTED )
           { // swap this with one below but only if it isn't selected
              EQCurve temp(wxT("temp"));
              temp.Name = mEditCurves[item].Name;
              temp.points = mEditCurves[item].points;
              mEditCurves[item].Name = mEditCurves[item+1].Name;
              mEditCurves[item].points = mEditCurves[item+1].points;
              mEditCurves[item+1].Name = temp.Name;
              mEditCurves[item+1].points = temp.points;
              wxString sTemp = mList->GetItemText(item);
              mList->SetItem(item, 0, mList->GetItemText(item+1));
              mList->SetItem(item+1, 0, sTemp);
              mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
              mList->SetItemState(item+1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
           }
        }
        item = GetPreviousItem(item);
     }
  }
  
  long EditCurvesDialog::GetPreviousItem(long item)  // wx doesn't have this
  {
     long lastItem = -1;
     long itemTemp = mList->GetNextItem(-1, wxLIST_NEXT_ALL,
        wxLIST_STATE_SELECTED);
     while( (itemTemp != -1) && (itemTemp < item) )
     {
        lastItem = itemTemp;
        itemTemp = mList->GetNextItem(itemTemp, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     }
     return lastItem;
  }
  
  // Rename curve/curves
  void EditCurvesDialog::OnRename(wxCommandEvent & WXUNUSED(event))
  {
     wxString name;
     int numCurves = mEditCurves.GetCount();
     int curve = 0;
  
     // Setup list of characters that aren't allowed
     wxArrayString exclude;
     exclude.Add( wxT("<") );
     exclude.Add( wxT(">") );
     exclude.Add( wxT("'") );
     exclude.Add( wxT("\"") );
  
     // Get the first one to be renamed
     long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     long firstItem = item;  // for reselection with PopulateList
     while(item >= 0)
     {
        // Prompt the user until a valid name is enter or cancelled
        bool overwrite = false;
        bool bad = true;
        while( bad )   // Check for an unacceptable duplicate
        {   // Show the dialog and bail if the user cancels
           bad = false;
           // build the dialog
           wxTextEntryDialog dlg( this,
              _("Rename '") + mEditCurves[ item ].Name + _("' to..."),
              _("Rename...") );
           dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
           dlg.SetName( _("Rename '") + mEditCurves[ item ].Name );
           wxTextValidator *tv = dlg.GetTextValidator();
           tv->SetExcludes( exclude );   // Tell the validator about excluded chars
           if( dlg.ShowModal() == wxID_CANCEL )
           {
              bad = true;
              break;
           }
  
           // Extract the name from the dialog
           name = dlg.GetValue();
  
           // Search list of curves for a duplicate name
           for( curve = 0; curve < numCurves; curve++ )
           {
              wxString temp = mEditCurves[ curve ].Name;
              if( name.IsSameAs( mEditCurves[ curve ].Name )) // case sensitive
              {
                 bad = true;
                 if( curve == item )  // trying to rename a curve with the same name
                 {
                    wxMessageBox( _("Name is the same as the original one"), _("Same name"), wxOK );
                    break;
                 }
                 int answer = wxMessageBox( _("Overwrite existing curve '") + name +_("'?"),
                    _("Curve exists"), wxYES_NO );
                 if (answer == wxYES)
                 {
                    bad = false;
                    overwrite = true; // we are going to overwrite the one with this name
                    break;
                 }
              }
           }
           if( name == wxT("") || name == wxT("unnamed") )
              bad = true;
        }
  
        // if bad, we cancelled the rename dialog, so nothing to do.
        if( bad == true )
           ;
        else if(overwrite){
           // Overwrite another curve.
           // JKC: because 'overwrite' is true, 'curve' is the number of the curve that
           // we are about to overwrite.
           mEditCurves[ curve ].Name = name;
           mEditCurves[ curve ].points = mEditCurves[ item ].points;
           // if renaming the unnamed item, then select it,
           // otherwise get rid of the item we've renamed.
           if( item == (numCurves-1) )
              mList->SetItem(curve, 0, name);
           else
           {
              mEditCurves.RemoveAt( item );
              numCurves--;
           }
        }
        else if( item == (numCurves-1) ) // renaming 'unnamed'
        {  // Create a new entry
           mEditCurves.Add( EQCurve( wxT("unnamed") ) );
           // Copy over the points
           mEditCurves[ numCurves ].points = mEditCurves[ numCurves - 1 ].points;
           // Give the original unnamed entry the new name
           mEditCurves[ numCurves - 1 ].Name = name;
           numCurves++;
        }
        else  // just rename (the 'normal' case)
        {
           mEditCurves[ item ].Name = name;
           mList->SetItem(item, 0, name);
        }
        // get next selected item
        item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     }
  
     PopulateList(firstItem);  // Note: only saved to file when you OK out of the dialog
     return;
  }
  
  // Delete curve/curves
  void EditCurvesDialog::OnDelete(wxCommandEvent & WXUNUSED(event))
  {
     // We could could count them here
     // And then put in a 'Delete N items?' prompt.
  
  #if 0 // 'one at a time' prompt code
     // Get the first one to be deleted
     long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     // Take care, mList and mEditCurves will get out of sync as curves are deleted
     int deleted = 0;
     long highlight = -1;
  
     while(item >= 0)
     {
        if(item == mList->GetItemCount()-1)   //unnamed
        {
           wxMessageBox(_("You cannot delete the 'unnamed' curve."),
              _("Can't delete 'unnamed'"), wxOK | wxCENTRE, this);
        }
        else
        {
           // Create the prompt
           wxString quest;
           quest = wxString(_("Delete '")) + mEditCurves[ item-deleted ].Name + _("' ?");
  
           // Ask for confirmation before removal
           int ans = wxMessageBox( quest, _("Confirm Deletion"), wxYES_NO | wxCENTRE, this );
           if( ans == wxYES )
           {  // Remove the curve from the array
              mEditCurves.RemoveAt( item-deleted );
              deleted++;
           }
           else
              highlight = item-deleted;  // if user presses 'No', select that curve
        }
        // get next selected item
        item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     }
  
     if(highlight == -1)
        PopulateList(mEditCurves.GetCount()-1);   // set 'unnamed' as the selected curve
     else
        PopulateList(highlight);   // user said 'No' to deletion
  #else // 'delete all N' code
     int count = mList->GetSelectedItemCount();
     long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     // Create the prompt
     wxString quest;
     if( count > 1 )
        quest.Printf(_("Delete ") + wxString(wxT("%d ")) + _("items?"), count);
     else
        if( count == 1 )
           quest = wxString(_("Delete '")) + mEditCurves[ item ].Name + _("' ?");
        else
           return;
     // Ask for confirmation before removal
     int ans = wxMessageBox( quest, _("Confirm Deletion"), wxYES_NO | wxCENTRE, this );
     if( ans == wxYES )
     {  // Remove the curve(s) from the array
        // Take care, mList and mEditCurves will get out of sync as curves are deleted
        int deleted = 0;
        while(item >= 0)
        {
           if(item == mList->GetItemCount()-1)   //unnamed
           {
              wxMessageBox(_("You cannot delete the 'unnamed' curve, it is special."),
                 _("Can't delete 'unnamed'"), wxOK | wxCENTRE, this);
           }
           else
           {
              mEditCurves.RemoveAt( item-deleted );
              deleted++;
           }
           item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
        }
        PopulateList(mEditCurves.GetCount()-1);   // set 'unnamed' as the selected curve
     }
  #endif
  }
  
  void EditCurvesDialog::OnImport( wxCommandEvent & WXUNUSED(event))
  {
     wxFileDialog filePicker(this, _("Choose an EQ curve file"), FileNames::DataDir(), wxT(""), _("xml files (*.xml;*.XML)|*.xml;*.XML"));
     wxString fileName = wxT("");
     if( filePicker.ShowModal() == wxID_CANCEL)
        return;
     else
        fileName = filePicker.GetPath();
     // Use EqualizationDialog::LoadCurves to read into (temporary) mEditCurves
     // This may not be the best OOP way of doing it, but I don't know better (MJS)
     EQCurveArray temp;
     temp = mParent->mCurves;   // temp copy of the main dialog curves
     mParent->mCurves = mEditCurves;  // copy EditCurvesDialog to main interface
     mParent->LoadCurves(fileName, true);   // use main interface to load imported curves
     mEditCurves = mParent->mCurves;  // copy back to this interface
     mParent->mCurves = temp;   // and reset the main interface how it was
     PopulateList(0);  // update the EditCurvesDialog dialog
     return;
  }
  
  void EditCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event))
  {
     wxFileDialog filePicker(this, _("Export EQ curves as..."), FileNames::DataDir(), wxT(""), wxT("*.XML"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER);   // wxFD_CHANGE_DIR?
     wxString fileName = wxT("");
     if( filePicker.ShowModal() == wxID_CANCEL)
        return;
     else
        fileName = filePicker.GetPath();
  
     EQCurveArray temp;
     temp = mParent->mCurves;   // backup the parent's curves
     EQCurveArray exportCurves;   // Copy selected curves to export
     exportCurves.Clear();
     long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     int i=0;
     while(item >= 0)
     {
        if(item != mList->GetItemCount()-1)   // not 'unnamed'
        {
           exportCurves.Add(mEditCurves[item].Name);
           exportCurves[i].points = mEditCurves[item].points;
           i++;
        }
        else
           wxMessageBox(_("You cannot export 'unnamed' curve, it is special."), _("Cannot Export 'unnamed'"));
        // get next selected item
        item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
     }
     if(i>0)
     {
        mParent->mCurves = exportCurves;
        mParent->SaveCurves(fileName);
        mParent->mCurves = temp;
        wxString message;
        message.Printf(_("%d curves exported to %s"), i, fileName.c_str());
        wxMessageBox(message, _("Curves exported"));
     }
     else
        wxMessageBox(_("No curves exported"), _("No curves exported"));
  }
  
  void EditCurvesDialog::OnLibrary( wxCommandEvent & WXUNUSED(event))
  {
     wxLaunchDefaultBrowser(wxT("http://wiki.audacityteam.org/wiki/EQCurvesDownload"));
  }
  
  void EditCurvesDialog::OnDefaults( wxCommandEvent & WXUNUSED(event))
  {
     EQCurveArray temp;
     temp = mParent->mCurves;
     // we expect this to fail in LoadCurves (due to a lack of path) and handle that there
     mParent->LoadCurves( wxT("EQDefaultCurves.xml") );
     mEditCurves = mParent->mCurves;
     mParent->mCurves = temp;
     PopulateList(0);  // update the EditCurvesDialog dialog
  }
  
  #ifdef EXPERIMENTAL_EQ_SSE_THREADED
  
  void EqualizationDialog::OnProcessingRadio(wxCommandEvent & event)
  {
     int testEvent=event.GetId();
     switch(testEvent)
     {
     case defaultMathRadioID: EffectEqualization48x::SetMathPath(MATH_FUNCTION_ORIGINAL);
        break;
     case sSERadioID: EffectEqualization48x::SetMathPath(MATH_FUNCTION_SSE);
        break;
     case sSEThreadedRadioID: EffectEqualization48x::SetMathPath(MATH_FUNCTION_THREADED | MATH_FUNCTION_SSE);
        break;
     case aVXRadioID: testEvent=2;
        break;
     case aVXThreadedRadioID: testEvent=2;
        break;
     }
  
  };
  
  void EqualizationDialog::OnBench( wxCommandEvent & event)
  {
     m_pEffect->mBench=true;
     OnOk(event);
  }
  
  #endif
  
  void EditCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event))
  {
     // Make a backup of the current curves
     wxString backupPlace = wxFileName( FileNames::DataDir(), wxT("EQBackup.xml") ).GetFullPath();
     mParent->SaveCurves(backupPlace);
     // Load back into the main dialog
     mParent->mCurves.Clear();
     for (unsigned int i = 0; i < mEditCurves.GetCount(); i++)
     {
        mParent->mCurves.Add(mEditCurves[i].Name);
        mParent->mCurves[i].points = mEditCurves[i].points;
     }
     mParent->SaveCurves();
     mParent->LoadCurves();
     mParent->CreateChoice();
     mParent->Layout();
  
     // Select something sensible
     long item = mList->GetNextItem(-1,
        wxLIST_NEXT_ALL,
        wxLIST_STATE_SELECTED);
     if (item == -1)
        item = mList->GetItemCount()-1;   // nothing selected, default to 'unnamed'
     mParent->setCurve(item);
     EndModal(true);
  }
  
  #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 WXUNUSED(childId), wxString *actionName )
  {
     actionName->Clear();
  
     return wxACC_OK;
  }
  
  // Returns the description for this object or a child.
  wxAccStatus SliderAx::GetDescription( int WXUNUSED(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 WXUNUSED(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 WXUNUSED(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 WXUNUSED(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 WXUNUSED(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 * WXUNUSED(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
  
  