/********************************************************************** Audacity: A Digital Audio Editor EffectEqualization.cpp Mitch Golden Vaughan Johnson (Preview) Martyn Shaw (FIR filters, response curve, graphic EQ) *******************************************************************//** \file EffectEqualization.cpp \brief Implements EffectEqualiztaion, EqualizationDialog, EqualizationPanel, EQCurve and EQPoint. *//****************************************************************//** \class EffectEqualization \brief An Effect. Performs filtering, using an FFT to do a FIR filter. It lets the user draw an arbitrary envelope (using the same envelope editing code that is used to edit the track's amplitude envelope). Also allows the curve to be specified with a series of 'graphic EQ' sliders. The filter is applied using overlap/add of Hanning windows. Has presets of certain specific equalization curves, suitable for old recordings. Clone of the FFT Filter effect, no longer part of Audacity. *//****************************************************************//** \class EqualizationDialog \brief Dialog used with EffectEqualization *//****************************************************************//** \class EqualizationPanel \brief EqualizationPanel is used with EqualizationDialog and controls a graph for EffectEqualization. We should look at amalgamating the various graphing code, such as provided by FreqWindow and FilterPanel. *//****************************************************************//** \class EQCurve \brief EQCurve is used with EffectEqualization. *//****************************************************************//** \class EQPoint \brief EQPoint is used with EQCurve and hence EffectEqualization. *//*******************************************************************/ #include "../Audacity.h" #include "Equalization.h" #include "../PlatformCompatibility.h" #include "../FileNames.h" #include "../Envelope.h" #include "../FFT.h" #include "../Prefs.h" #include "../Project.h" #include "../WaveTrack.h" #include "../widgets/Ruler.h" #include "../xml/XMLFileReader.h" #include "../Theme.h" #include "../AllThemeResources.h" #include "float_cast.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if wxUSE_TOOLTIPS #include #endif #include #include #include WX_DEFINE_OBJARRAY( EQPointArray ); WX_DEFINE_OBJARRAY( EQCurveArray ); const double EqualizationDialog::thirdOct[] = { 20., 25., 31., 40., 50., 63., 80., 100., 125., 160., 200., 250., 315., 400., 500., 630., 800., 1000., 1250., 1600., 2000., 2500., 3150., 4000., 5000., 6300., 8000., 10000., 12500., 16000., 20000., }; const float EffectEqualization::curvex[] = { 30., 31., 50., 63., 70., 100., 125., 200., 250., 300., 400., 500., 600., 700., 800., 900., 1000., 2000., 3000., 4000., 5000., 6000., 7000., 8000., 9000., 10000., 15000., 16000. }; // Don't want to be warned about double->float precision loss here // when compiling with MSVC. #if defined(__WXMSW__) #pragma warning( disable: 4305 ) #endif const float EffectEqualization::curvey[][nCurvePoints] = { { // amradio // 30 31 50 63 70 100 125 200 250 300 400 500 600 700 -20.,-20.,-20.,-20.,-20.,-20.,-16.,-12., -8., -4., 0.0, 0.0, 0.0, 0.0, // 800 900 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 15000 16000. 0.0, 0.0, 0.0, -4., -8.,-12.,-16.,-20.0,-20.,-20.,-20.,-20., -20., -20.0 }, { // acoustic (see p 52) -20.0, -20.0, -20.0, 5.0, 4.4, 3.3, 2.5, 1.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.5, -2.5, -3.3, -4.0, -4.5, -5.0, -10.0, -15.0, -20.0, -20.0 }, { // NAB 20.0, 20.0, 16.0, 15.6, 15.5, 13.8, 12.0, 8.0, 6.2, 5.0, 3.0, 1.7, 1.0, 0.0, -0.5, -1.0, -1.3, -4.2, -6.5, -8.5, -10.2, -12.0, -13.0, -14.0, -15.0, -16.0, -20.0, -20.0 }, { // LP 13.5, 13.2, 13.0, 12.8, 12.5, 11.0, 10.5, 8.0, 7.0, 6.0, 3.5, 2.5, 1.5, 1.0, 0.5, -0.5, -1.0, -3.5, -6.0, -8.0, -10.0, -11.5, -12.5, -13.5, -14.5, -16.0, -21.2, -22.0 }, { // AES 22.5, 22.5, 18.0, 16.0, 15.0, 12.0, 10.0, 6.5, 5.2, 4.5, 3.0, 2.0, 1.5, 1.0, 0.5, 0.0, 0.0, -2.2, -4.0, -5.5, -6.7, -8.0, -9.0, -10.0, -11.0, -12.0, -15.5, -16.0 }, { // Decca FFRR Micro 14.0, 14.0, 14.0, 13.8, 13.5, 12.5, 11.5, 8.5, 7.2, 6.0, 4.0, 2.5, 1.5, 1.0, 0.5, 0.0, 0.0, -1.5, -3.0, -4.5, -6.0, -7.0, -8.0, -8.5, -9.0, -10.0, -12.6, -13.0 }, { // Decca FFRR 78 22.0, 21.5, 14.0, 11.2, 9.8, 6.0, 2,0, 1.5, 1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 -0.5, -1.0, -2.0, -2.5, -3.5, -4.0, -4.5, -7.0, -7.5 }, { // RIAA 18.6, 18.5, 16.9, 15.9, 15.3, 13.1, 11.6, 8.2, 6.7, 5.5, 3.8, 2.6, 1.8, 1.2, 0.8, 0.3, 0.0, -2.6, -4.7, -6.6, -8.2, -9.6, -10.8, -11.9, -12.9, -13.7, -17.2, -17.7 }, { // Col 78 16.0, 16.0, 16.0, 14.0, 12.5, 10.0, 8.5, 5.0, 4.0, 3.0, 2.0, 1.0, 0.5, 0.2, 0.0, -0.5, -1.0, -3.5, -6.0, -8.0, -10.0, -11.5, -12.5, -13.5, -14.5, -16.0, -21.2, -22.0 }, { // Decca FFRR LP 17.5, 17.2, 14.0, 12.0, 11.5, 9.0, 7.5, 5.0, 4.0, 3.0, 2.0, 1.5, 1.0, 0.7, 0.2, 0.0, 0.0, -4.0, -6.7, -8.5, -10.0, -11.0, -12.0, -13.0, -13.2, -14.0, -16.0, -16.0 }, { // EMI 78 14.0, 14.0, 14.0, 12.0, 11.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -5.0, -5.0 }, { // RCA Victor 1938 24.0, 24.0, 24.0, 21.8, 20.0, 16.0, 13.0, 9.0, 7.5, 6.0, 4.0, 3.0, 2.0, 1.5, 1.0, 0.5, 0.0, -2.5, -5.0, -6.5, -7.5, -8.0, -7.7, -7.5, -7.5, -7.5, -7.5, -7.5 }, { // RCA Victor 1947 24.0, 24.0, 24.0, 21.8, 20.0, 16.0, 13.0, 9.0, 7.5, 6.0, 4.0, 3.0, 2.0, 1.5, 1.0, 0.5, 0.0, -2.5, -5.0, -6.5, -8.0, -10.0, -11.5, -12.0, -12.5, -12.5, -12.5, -12.5 } }; #if defined(__WXMSW__) #pragma warning( default: 4305 ) #endif #ifdef __WXGTK__ /* * wxSlider exhibits strange behaviour on wxGTK when wxSL_INVERSE and/or * negative scale values are used. This affects at least SUSE 9.3 with * self-compiled wxWidgets 2.6.2 and Kubuntu 6.06. This class is used * to transparently work around those problems. Use wxSliderBugfix wherever * you would have used a slider with wxSL_INVERSE and/or negative scale * values. * * The equalization class uses wxSliderBugfix everywhere even when not * strictly needed. This is important because we override some wxSlider * functions which are not virtual, and so we need to cast to wxSliderBugfix * instead of to wxSlider to allow the right function to be called statically. */ class wxSliderBugfix : public wxSlider { public: wxSliderBugfix(wxWindow* parent, wxWindowID id, int value, int minValue, int maxValue, const wxPoint& point = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxSL_HORIZONTAL, const wxValidator& validator = wxDefaultValidator, const wxString& name = wxT("slider")) : wxSlider(parent, id, 0, 0, maxValue - minValue, point, size, style & ~wxSL_INVERSE, validator, name) { isInverse = (style & wxSL_INVERSE) != 0; originalMinValue = minValue; SetValue(value); } int GetMin() const { return originalMinValue; } int GetMax() const { return wxSlider::GetMax() + originalMinValue; } int GetValue() const { if (isInverse) return wxSlider::GetMax() - wxSlider::GetValue() + originalMinValue; else return wxSlider::GetValue() + originalMinValue; } void SetValue(int value) { if (isInverse) wxSlider::SetValue(wxSlider::GetMax() - value + originalMinValue); else wxSlider::SetValue(value - originalMinValue); } private: bool isInverse; int originalMinValue; }; #else /* * On platforms other than wxGTK, the workaround is not needed */ #define wxSliderBugfix wxSlider #endif // ifdef __WXGTK__ const wxChar * EffectEqualization::curveNames[] = { wxT("amradio"), wxT("acoustic"), wxT("NAB"), wxT("Columbia LP"), wxT("AES"), wxT("Decca FFRR Micro"), wxT("Decca FFRR 78"), wxT("RIAA"), wxT("Columbia 78"), wxT("Decca FFRR LP"), wxT("EMI 78"), wxT("RCA Victor 1938"), wxT("RCA Victor 1947") }; #define NUM_INTERP_CHOICES 3 static wxString interpChoiceStrings[NUM_INTERP_CHOICES] = { _("B-spline"), _("Cosine"), _("Cubic") }; EffectEqualization::EffectEqualization() { mFilterFuncR = new float[windowSize]; mFilterFuncI = new float[windowSize]; gPrefs->Read(wxT("/CsPresets/EQFilterLength"), &mM, 4001); if ((mM < 21) || (mM > 8191)) { // corrupted Prefs? mM = 4001; //default gPrefs->Write(wxT("/CsPresets/EQFilterLength"), mM); } gPrefs->Read(wxT("/CsPresets/EQCurveName"), &mCurveName, wxT("custom")); gPrefs->Read(wxT("/CsPresets/EQLin"), &mLin, false); gPrefs->Read(wxT("/CsPresets/EQdBMin"), &mdBMin, -30.0); if ((mdBMin < -120) || (mdBMin > -10)) { // corrupted Prefs? mdBMin = -30; //default gPrefs->Write(wxT("/CsPresets/EQFilterLength"), mdBMin); } gPrefs->Read(wxT("/CsPresets/EQdBMax"), &mdBMax, 30.); if ((mdBMax < 0) || (mdBMax > 60)) { // corrupted Prefs? mdBMax = 30; //default gPrefs->Write(wxT("/CsPresets/EQFilterLength"), mdBMax); } gPrefs->Read(wxT("/CsPresets/EQDrawMode"), &mDrawMode, true); gPrefs->Read(wxT("/CsPresets/EQInterp"), &mInterp, 0); } EffectEqualization::~EffectEqualization() { if(mFilterFuncR) delete[] mFilterFuncR; if(mFilterFuncI) delete[] mFilterFuncI; mFilterFuncR = NULL; mFilterFuncI = NULL; } bool EffectEqualization::PromptUser() { TrackListIterator iter(mWaveTracks); 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, mParent, -1, _("Equalization")); dlog.M = mM; dlog.curveName = mCurveName; dlog.linCheck = mLin; dlog.dBMin = mdBMin; dlog.dBMax = mdBMax; dlog.drawMode = mDrawMode; dlog.interp = mInterp; // not required here - called automatically // dlog.TransferDataToWindow(); dlog.CentreOnParent(); dlog.ShowModal(); 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; gPrefs->Write(wxT("/CsPresets/EQFilterLength"),mM); gPrefs->Write(wxT("/CsPresets/EQCurveName"),mCurveName); gPrefs->Write(wxT("/CsPresets/EQLin"),mLin); gPrefs->Write(wxT("/CsPresets/EQdBMin"),mdBMin); gPrefs->Write(wxT("/CsPresets/EQdBMax"),mdBMax); gPrefs->Write(wxT("/CsPresets/EQDrawMode"),mDrawMode); gPrefs->Write(wxT("/CsPresets/EQInterp"), mInterp); return true; } bool EffectEqualization::DontPromptUser() { TrackListIterator iter(mWaveTracks); 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, mParent, -1, _("Equalization")); dlog.M = mM; dlog.curveName = mCurveName; dlog.linCheck = mLin; dlog.interp = mInterp; // IS required here - no call to ShowModal! dlog.TransferDataToWindow(); dlog.CalcFilter(); 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); return true; } bool EffectEqualization::Process() { DontPromptUser(); TrackListIterator iter(mWaveTracks); 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) { longSampleCount start = track->TimeToLongSamples(t0); longSampleCount end = track->TimeToLongSamples(t1); sampleCount len = (sampleCount)(end - start); if (!ProcessOne(count, track, start, len)) return false; } track = (WaveTrack *) iter.Next(); count++; } return true; } bool EffectEqualization::ProcessOne(int count, WaveTrack * t, sampleCount start, sampleCount len) { 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; for(i=0; i len) block = len; t->Get((samplePtr)buffer, floatSample, s, block); int j = 0; for(i=0; i block) //if last lump would exceed block wcopy = block - i; //shorten it for(j=0; jSet((samplePtr)buffer, floatSample, s, block); len -= block; s += block; TrackProgress(count, (s-start)/(double)originalLen); } delete[] buffer; delete[] window1; delete[] window2; return true; } void EffectEqualization::Filter(sampleCount len, float *buffer) { float *inr = new float[len]; float *ini = new float[len]; float *outr = new float[len]; float *outi = new float[len]; int i; float temp; for(i=0; iFlatten(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 FFT(mWindowSize,true,mFilterFuncR,mFilterFuncI,mOutr,mOuti); //work out FIR response } void EqualizationPanel::OnSize(wxSizeEvent & evt) { Refresh( false ); } void EqualizationPanel::OnPaint(wxPaintEvent & evt) { wxPaintDC dc(this); if(RecalcRequired) { Recalc(); RecalcRequired = false; } int width, height; GetSize(&width, &height); if (!mBitmap || mWidth!=width || mHeight!=height) { if (mBitmap) delete mBitmap; mWidth = width; mHeight = height; mBitmap = new wxBitmap(mWidth, mHeight); } wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); wxMemoryDC memDC; memDC.SelectObject(*mBitmap); wxRect bkgndRect; bkgndRect.x = 0; bkgndRect.y = 0; bkgndRect.width = mWidth; bkgndRect.height = mHeight; memDC.SetBrush(bkgndBrush); memDC.SetPen(*wxTRANSPARENT_PEN); memDC.DrawRectangle(bkgndRect); bkgndRect.y = mHeight; memDC.DrawRectangle(bkgndRect); wxRect border; border.x = 0; border.y = 0; border.width = mWidth; border.height = mHeight; memDC.SetBrush(*wxWHITE_BRUSH); memDC.SetPen(*wxBLACK_PEN); memDC.DrawRectangle(border); mEnvRect = border; mEnvRect.Deflate( 2, 2 ); // Pure blue x-axis line memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID)); int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5); memDC.DrawLine(mEnvRect.x, mEnvRect.y + center, mEnvRect.x + mEnvRect.width, mEnvRect.y + center); // Med-blue envelope line memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 3, wxSOLID)); // Draw envelope double *values = new double[mEnvRect.width]; mEnvelope->GetValues(values, mEnvRect.width, 0.0, 1.0/mEnvRect.width); int x, y, xlast = 0, ylast = 0; bool off = false, off1 = false; for(int i=0; i mEnvRect.height) { y = mEnvRect.height; off = true; } else { off = false; off1 = false; } if ( (i != 0) & (!off1) ) { memDC.DrawLine(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; imEnvRect.height) yF = mEnvRect.height; if(yF<0.) yF=0.; y = (int)(yF+.5); if (i != 0) { memDC.DrawLine(xlast, ylast, x, mEnvRect.y + y); } xlast = x; ylast = mEnvRect.y + y; } memDC.SetPen(*wxBLACK_PEN); if( mParent->mFaderOrDraw[0]->GetValue() ) mEnvelope->Draw(memDC, mEnvRect, 0.0, mEnvRect.width, 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, dBMin, dBMax)) { mParent->EnvelopeUpdated(); RecalcRequired = true; Refresh(false); } if (event.ButtonUp() && HasCapture()) { ReleaseMouse(); } } // WDR: class implementations //---------------------------------------------------------------------------- // EqualizationDialog //---------------------------------------------------------------------------- // WDR: event table for EqualizationDialog BEGIN_EVENT_TABLE(EqualizationDialog,wxDialog) EVT_SIZE( EqualizationDialog::OnSize ) EVT_PAINT( EqualizationDialog::OnPaint ) EVT_ERASE_BACKGROUND( EqualizationDialog::OnErase ) EVT_SLIDER( ID_LENGTH, EqualizationDialog::OnSliderM ) EVT_SLIDER( ID_DBMAX, EqualizationDialog::OnSliderDBMAX ) EVT_SLIDER( ID_DBMIN, EqualizationDialog::OnSliderDBMIN ) EVT_COMMAND_RANGE( ID_SLIDER, ID_SLIDER + NUMBER_OF_BANDS - 1, wxEVT_COMMAND_SLIDER_UPDATED, EqualizationDialog::OnSlider) EVT_CHOICE( ID_INTERP, EqualizationDialog::OnInterp ) EVT_CHOICE( ID_CURVE, EqualizationDialog::OnCurve ) EVT_BUTTON( ID_SAVEAS, EqualizationDialog::OnSaveAs ) EVT_BUTTON( ID_DELETE, EqualizationDialog::OnDelete ) EVT_BUTTON( ID_CLEAR, EqualizationDialog::OnClear ) EVT_BUTTON( ID_PREVIEW, EqualizationDialog::OnPreview ) EVT_BUTTON( wxID_OK, EqualizationDialog::OnOk ) EVT_BUTTON( wxID_CANCEL, EqualizationDialog::OnCancel ) EVT_RADIOBUTTON(drawRadioID, EqualizationDialog::OnDrawRadio) EVT_RADIOBUTTON(sliderRadioID, EqualizationDialog::OnSliderRadio) EVT_CHECKBOX(ID_LIN_FREQ, EqualizationDialog::OnLinFreq) END_EVENT_TABLE() EqualizationDialog::EqualizationDialog(EffectEqualization * effect, double loFreq, double hiFreq, float *filterFuncR, float *filterFuncI, long windowSize, wxString curveName, 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 ) { m_pEffect = effect; M = 4001; linCheck = false; dBMin = -30.; dBMax = 30; #if wxUSE_TOOLTIPS wxToolTip::Enable(true); #endif mLogEnvelope = new Envelope(); mLogEnvelope->SetInterpolateDB(false); mLogEnvelope->Mirror(false); mLinEnvelope = new Envelope(); mLinEnvelope->SetInterpolateDB(false); mLinEnvelope->Mirror(false); mLoFreq = loFreq; mHiFreq = hiFreq; mFilterFuncR = filterFuncR; mFilterFuncI = filterFuncI; mWindowSize = windowSize; mCurve = NULL; mDirty = false; // Load the EQ curves LoadCurves(); // Create the dialog MakeEqualizationDialog(); // Set initial curve // not needed - done in TransferDataToWindow // setCurve( mLogEnvelope, curveName ); bandsInUse = NUMBER_OF_BANDS; //double loLog = log10(mLoFreq); //double stepLog = (log10(mHiFreq) - loLog)/((double)NUM_PTS-1.); for(int i=0; iAdd( txt, 0, wxALIGN_CENTRE|wxALL, 4 ); // ------------------------------------------------------------------- // ROW 2: EQ panel and sliders for vertical scale // ------------------------------------------------------------------- szr1 = new wxFlexGridSizer( 4, 0, 0 ); szr1->AddGrowableCol( 2, 1 ); szr1->AddGrowableRow( 0, 1 ); szr1->SetFlexibleDirection( wxBOTH ); szr2 = new wxBoxSizer( wxVERTICAL ); dBMaxSlider = new wxSliderBugfix(this, ID_DBMAX, 30, 0, 60, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); dBMinSlider = new wxSliderBugfix(this, ID_DBMIN, -30, -120, -10, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); szr2->Add( dBMinSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); szr1->Add( szr2, 0, wxEXPAND|wxALIGN_CENTRE|wxALL, 4 ); #if wxUSE_ACCESSIBILITY dBMaxSlider->SetName(_("Max dB")); dBMaxSlider->SetAccessible(new SliderAx(dBMaxSlider, wxString(wxT("%d ")) + _("dB"))); dBMinSlider->SetName(_("Min dB")); dBMinSlider->SetAccessible(new SliderAx(dBMinSlider, wxString(wxT("%d ")) + _("dB"))); #endif dBRuler = new RulerPanel(this, wxID_ANY); dBRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes dBRuler->ruler.SetOrientation(wxVERTICAL); dBRuler->ruler.SetRange(60.0, -120.0); dBRuler->ruler.SetFormat(Ruler::LinearDBFormat); dBRuler->ruler.SetUnits(_("dB")); dBRuler->ruler.SetLabelEdges(true); int w, h; dBRuler->ruler.GetMaxSize(&w, NULL); dBRuler->SetSize(wxSize(w, 150)); // height needed for wxGTK szr4 = new wxBoxSizer( wxVERTICAL ); szr4->AddSpacer(2); // vertical space for panel border and thickness of line szr4->Add( dBRuler, 1, wxEXPAND|wxALIGN_LEFT|wxALL ); szr4->AddSpacer(1); // vertical space for thickness of line szr1->Add( szr4, 0, wxEXPAND|wxALIGN_LEFT|wxALL ); mPanel = new EqualizationPanel( mLoFreq, mHiFreq, mLogEnvelope, this, mFilterFuncR, mFilterFuncI, mWindowSize, ID_FILTERPANEL); szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE|wxRIGHT, 4); szr3 = new wxBoxSizer( wxVERTICAL ); szr1->Add( szr3, 0, wxALIGN_CENTRE|wxRIGHT, 0); //spacer for last EQ /// Next row of wxFlexGridSizer szr1->Add(1, 1); // horizontal spacer szr1->Add(1, 1); // horizontal spacer freqRuler = new RulerPanel(this, wxID_ANY); freqRuler->ruler.SetBounds(0, 0, 100, 100); // Ruler can't handle small sizes freqRuler->ruler.SetOrientation(wxHORIZONTAL); freqRuler->ruler.SetLog(true); freqRuler->ruler.SetRange(mLoFreq, mHiFreq); freqRuler->ruler.SetFormat(Ruler::IntFormat); freqRuler->ruler.SetUnits(_("Hz")); freqRuler->ruler.SetFlip(true); freqRuler->ruler.SetLabelEdges(true); freqRuler->ruler.GetMaxSize(NULL, &h); freqRuler->SetMinSize(wxSize(-1, h)); szr1->Add( freqRuler, 0, wxEXPAND|wxALIGN_LEFT|wxRIGHT, 4 ); szrV->Add( szr1, 1, wxEXPAND|wxALIGN_CENTER|wxALL, 0 ); // ------------------------------------------------------------------- // ROW 3: Graphic EQ - this gets laid out horizontally in onSize // ------------------------------------------------------------------- szrG = new wxBoxSizer( wxHORIZONTAL ); szrG->Add(0, 0, 0); // horizontal spacer, will be used to position LH EQ slider for (int i = 0; thirdOct[i] <= mHiFreq; ++i) { if( i == NUMBER_OF_BANDS ) break; m_sliders[i] = new wxSliderBugfix(this, ID_SLIDER + i, 0, -20, +20, wxDefaultPosition, wxSize(20, 124), wxSL_VERTICAL| wxSL_INVERSE); szrG->Add( m_sliders[i], 0, wxEXPAND|wxALIGN_CENTER ); szrG->Add(0, 0, 0); // horizontal spacer - used to put EQ sliders in correct position m_sliders[i]->Connect(wxEVT_ERASE_BACKGROUND,wxEraseEventHandler(EqualizationDialog::OnErase)); m_sliders_old[i] = 0; m_EQVals[i] = 0.; #if wxUSE_ACCESSIBILITY wxString name; if( thirdOct[i] < 1000.) name.Printf(wxString(wxT("%d ")) + _("Hz"), (int)thirdOct[i]); else name.Printf(wxString(wxT("%g ")) + _("kHz"), thirdOct[i]/1000.); m_sliders[i]->SetName(name); m_sliders[i]->SetAccessible(new SliderAx(m_sliders[i], wxString(wxT("%d ")) + _("dB"))); #endif } szrV->Add( szrG, 0, wxEXPAND|wxALIGN_LEFT|wxALL, 0 ); wxSizerItem *EQslider = szrG->GetItem((size_t)1); wxSize EQsliderSize = EQslider->GetSize(); //size of the sliders szr3->SetMinSize(EQsliderSize.x/2, -1); //extra gap for last slider // ------------------------------------------------------------------- // ROW 4a: Graphic or curve drawing? // ------------------------------------------------------------------- szrH = new wxBoxSizer( wxHORIZONTAL ); mFaderOrDraw[0] = new wxRadioButton( this, drawRadioID, _("Draw curves"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); szrH->Add( mFaderOrDraw[0], 0, wxRIGHT, 10 ); mFaderOrDraw[1] = new wxRadioButton( this, sliderRadioID, _("Graphic EQ"), wxDefaultPosition, wxDefaultSize, 0 ); szrH->Add( mFaderOrDraw[1], 0, wxRIGHT, 4 ); mInterpChoice = new wxChoice(this, ID_INTERP, wxDefaultPosition, wxDefaultSize, NUM_INTERP_CHOICES, interpChoiceStrings); mInterpChoice->SetSelection(0); szrI = new wxBoxSizer( wxHORIZONTAL ); szrI->Add( mInterpChoice, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 40 ); szrH->Add( szrI ); szrL = new wxBoxSizer( wxHORIZONTAL ); mLinFreq = new wxCheckBox(this, ID_LIN_FREQ, _("Linear frequency scale")); szrL->Add( mLinFreq, 0 ); szrH->Add(szrL); // either szrI or szrL are visible, not both. // ------------------------------------------------------------------- // ROW 4b: Filter length grouping // ------------------------------------------------------------------- // length of filter (M) label txt = new wxStaticText(this, wxID_ANY, _("Length of filter:")); szrH->Add( txt, 0 ); // length of filter (M) slider MSlider = new wxSliderBugfix(this, ID_LENGTH, (M -1)/2, 10, 4095, wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL); szrH->Add( MSlider, 0, wxEXPAND ); wxString label; label.Printf( wxT("%d"), M ); mMText = new wxStaticText(this, wxID_ANY, label); szrH->Add( mMText, 0 ); // Add the length / graphic / draw grouping szrV->Add( szrH, 0, wxALIGN_CENTER | wxALL, 4 ); // ------------------------------------------------------------------- // ROW 5: Curve management grouping // ------------------------------------------------------------------- szrC = new wxBoxSizer( wxHORIZONTAL ); //szrC is for the curves bits txt = new wxStaticText( this, wxID_ANY, _("Select curve:") ); szrC->Add( txt, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); // Create the choice sizer (helps in recreating choice control) mCurveSizer = new wxBoxSizer( wxHORIZONTAL ); szrC->Add( mCurveSizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 4 ); // Create the choice control CreateChoice(); mSaveAs = new wxButton( this, ID_SAVEAS, _("Save As...") ); szrC->Add( mSaveAs, 0, wxALIGN_CENTRE|wxLEFT, 4 ); mDelete = new wxButton( this, ID_DELETE, _("Delete") ); szrC->Add( mDelete, 0, wxALIGN_CENTRE|wxLEFT, 4 ); btn = new wxButton( this, ID_CLEAR, _("Flat")); szrC->Add( btn, 0, wxALIGN_CENTRE | wxALL, 4 ); szrV->Add( szrC, 0, wxALIGN_CENTER | wxALL, 0 ); // ------------------------------------------------------------------- // ROW 6: Preview, OK, & Cancel buttons // ------------------------------------------------------------------- szrJ = new wxBoxSizer(wxHORIZONTAL); btn = new wxButton(this, ID_PREVIEW, m_pEffect->GetPreviewName()); szrJ->Add( btn, 0, wxALIGN_LEFT | wxALL, 4 ); szrJ->Add(80, 4); // horizontal spacer btn = new wxButton(this, wxID_CANCEL, _("&Cancel")); szrJ->Add( btn, 0, wxALIGN_RIGHT | wxALL, 4 ); btn = new wxButton(this, wxID_OK, _("&OK")); btn->SetDefault(); szrJ->Add( btn, 0, wxALIGN_RIGHT | wxALL, 4 ); szrV->Add( szrJ, 0, wxALIGN_CENTER ); // ------------------------------------------------------------------- // Display now // ------------------------------------------------------------------- SetAutoLayout(false); szrV->Show(szrG,true); szrH->Show(szrI,true); szrH->Show(szrL,false); SetSizerAndFit( szrV ); SetSizeHints(GetSize()); szrL->SetMinSize( szrI->GetSize() ); szrV->Show(szrC,true); szrV->Show(szrG,false); szrH->Show(szrI,false); szrH->Show(szrL,true); return; } // // (Re)Create the choice control // void EqualizationDialog::CreateChoice() { wxChoice *choice; int i; int numCurves = mCurves.GetCount(); wxString *curveNames = new wxString[ numCurves ]; // Create an array of names for( i = 0; i < numCurves; i++ ) { curveNames[ i ] = mCurves[ i ].Name; } // Create the control choice = new wxChoice( this, ID_CURVE, wxDefaultPosition, wxDefaultSize, numCurves, curveNames ); // Have an existing control? if( mCurve ) { // Then detach it from its sizer and delete it mCurveSizer->Detach( mCurve ); delete mCurve; } // Save control ptr and add to its sizer mCurve = choice; mCurveSizer->Add( mCurve, 0 ); // Delete the array of names delete [] curveNames; } // // Validate data (A do nothing routine) // bool EqualizationDialog::Validate() { 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); 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( mLogEnvelope, curveName ); // Set graphic interpolation mode mInterpChoice->SetSelection(interp); // Set Graphic (Fader) or Draw mode if(drawMode) { mFaderOrDraw[0]->SetValue(true); } else { mFaderOrDraw[1]->SetValue(true); UpdateGraphic(); } return TransferDataFromWindow(); } // // Retrieve data from the window // bool EqualizationDialog::TransferDataFromWindow() { // Read the sliders and send to the panel wxString tip; bool rr = false; int dB = dBMinSlider->GetValue(); if (dB != dBMin) { rr = true; dBMin = dB; mPanel->dBMin = dBMin; #if wxUSE_TOOLTIPS tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMin); dBMinSlider->SetToolTip(tip); #endif } dB = dBMaxSlider->GetValue(); if (dB != dBMax) { rr = true; dBMax = dB; mPanel->dBMax = dBMax; #if wxUSE_TOOLTIPS tip.Printf(wxString(wxT("%d ")) + _("dB"),(int)dBMax); dBMaxSlider->SetToolTip(tip); #endif } // Refresh ruler if values have changed if (rr) { int w1, w2, h; dBRuler->ruler.GetMaxSize(&w1, &h); dBRuler->ruler.SetRange(dBMax, dBMin); dBRuler->ruler.GetMaxSize(&w2, &h); if( w1 != w2 ) // Reduces flicker { dBRuler->SetSize(wxSize(w2,h)); szr1->Layout(); LayoutEQSliders(); szrG->Layout(); freqRuler->Refresh(false); } dBRuler->Refresh(false); } int m = 2 * MSlider->GetValue() + 1; // odd numbers only if (m != M) { M = m; mPanel->M = M; mMText->SetLabel(wxString::Format(wxT("%d"), M)); #if wxUSE_TOOLTIPS tip.Printf(wxT("%d"), M); MSlider->SetToolTip(tip); #endif } mPanel->Refresh(false); return true; } bool EqualizationDialog::CalcFilter() { double loLog = log10(mLoFreq); double hiLog = log10(mHiFreq); double denom = hiLog - loLog; double delta = mHiFreq / ((double)(mWindowSize/2.)); double val0; double val1; bool lin = mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked(); if( lin ) { val0 = mLinEnvelope->GetValue(0.0); //no scaling required - saved as dB val1 = mLinEnvelope->GetValue(1.0); } else { val0 = mLogEnvelope->GetValue(0.0); //no scaling required - saved as dB val1 = mLogEnvelope->GetValue(1.0); } mFilterFuncR[0] = val0; double freq = delta; int i; for(i=1; i<=mWindowSize/2; i++) { double when; if( lin ) when = freq/mHiFreq; else when = (log10(freq) - loLog)/denom; if(when < 0.) { mFilterFuncR[i] = val0; } else if(when > 1.0) { mFilterFuncR[i] = val1; } else { if( lin ) mFilterFuncR[i] = mLinEnvelope->GetValue(when); else mFilterFuncR[i] = mLogEnvelope->GetValue(when); } freq += delta; } mFilterFuncR[mWindowSize/2] = val1; mFilterFuncR[0] = (float)(pow(10., mFilterFuncR[0]/20.)); for(i=1;iFlatten(0.); env->SetTrackLen(1.0); wxASSERT( currentCurve < (int) mCurves.GetCount() ); if( mCurves[currentCurve].points.GetCount() ) { double when = 0.; double value = mCurves[currentCurve].points[0].dB; env->Move(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;iInsert(when, value); else break; } when = 1.; value = mCurves[currentCurve].points[nCurvePoints-1].dB; env->Move(when, value); } mPanel->RecalcRequired = true; mPanel->Refresh( false ); } void EqualizationDialog::setCurve(Envelope *env) { setCurve( env, (int) mCurves.GetCount()-1); } void EqualizationDialog::setCurve(Envelope *env, wxString curveName) { unsigned i = 0; for( i = 0; i < mCurves.GetCount(); i++ ) if( curveName == mCurves[ i ].Name ) break; if( i == mCurves.GetCount()) { wxMessageBox( wxT("Requested curve not found, using 'custom'"), wxT("Curve not found"), wxOK|wxICON_ERROR ); setCurve( env, (int) mCurves.GetCount()-1); } else setCurve( env, i ); } // // Set new curve selection and manage state of delete button // void EqualizationDialog::Select( int curve ) { // Set current choice mCurve->SetSelection( curve ); curveName = mCurves[ curve ].Name; // If the "custom" curve became active if( curve == mCurve->GetCount() - 1 ) { // Prevent focus from being lost if( mDelete->FindFocus() == mDelete ) { mCurve->SetFocus(); } // Not allowed to delete the "custom" curve mDelete->Disable(); } else { // Allow the curve to be deleted mDelete->Enable(); } } // // Capture updated envelope // void EqualizationDialog::EnvelopeUpdated() { EnvelopeUpdated(mLogEnvelope); } void EqualizationDialog::EnvelopeUpdated(Envelope *env) { // Allocate and populate point arrays int numPoints = env->GetNumberOfPoints(); double *when = new double[ numPoints ]; double *value = new double[ numPoints ]; env->GetPoints( when, value, numPoints ); // Clear the custom curve #if wxCHECK_VERSION(2, 6, 2) && !defined(__WXX11__) int curve = mCurve->GetCurrentSelection(); #else int curve = mCurve->GetSelection(); #endif mCurves[ curve ].points.Clear(); double loLog = log10( 20. ); double hiLog = log10( mHiFreq ); double denom = hiLog - loLog; // Copy and convert points int point; for( point = 0; point < numPoints; point++ ) { double freq = pow( 10., ( ( when[ point ] * denom ) + loLog )); double db = value[ point ]; // Add it to the curve mCurves[ curve ].points.Add( EQPoint( freq, db ) ); } // Remember that we've updated the custom curve mDirty = true; // set 'custom' as the selected curve Select( (int) mCurves.GetCount()-1 ); // Clean up delete [] when; delete [] value; } // // Process XML tags and handle the ones we recognize // bool EqualizationDialog::HandleXMLTag(const wxChar *tag, const wxChar **attrs) { // May want to add a version strings... if( !wxStrcmp( tag, wxT("equalizationeffect") ) ) { return true; } // Located a new curve if( !wxStrcmp(tag, wxT("curve") ) ) { // Process the attributes while( *attrs ) { // Cache attr/value and bump to next const wxChar *attr = *attrs++; const wxChar *value = *attrs++; // Create a new curve and name it if( !wxStrcmp( attr, wxT("name") ) ) { const wxString strValue = value; if (!XMLValueChecker::IsGoodString(strValue)) return false; mCurves.Add( EQCurve( strValue ) ); } } // Tell caller it was processed return true; } // Located a new point if( !wxStrcmp( tag, wxT("point") ) ) { // Set defaults in case attributes are missing double f = 0.0; double d = 0.0; // Process the attributes double dblValue; while( *attrs ) { // Cache attr/value and bump to next const wxChar *attr = *attrs++; const wxChar *value = *attrs++; const wxString strValue = value; // Get the frequency if( !wxStrcmp( attr, wxT("f") ) ) { if (!XMLValueChecker::IsGoodString(strValue) || !Internat::CompatibleToDouble(strValue, &dblValue)) return false; f = dblValue; } // Get the dB else if( !wxStrcmp( attr, wxT("d") ) ) { if (!XMLValueChecker::IsGoodString(strValue) || !Internat::CompatibleToDouble(strValue, &dblValue)) return false; d = dblValue; } } // Create a new point mCurves[ mCurves.GetCount() - 1 ].points.Add( EQPoint( f, d ) ); // Tell caller it was processed return true; } // Tell caller we didn't understand the tag return false; } // // Return handler for recognized tags // XMLTagHandler *EqualizationDialog::HandleXMLChild(const wxChar *tag) { if( !wxStrcmp( tag, wxT("equalizationeffect") ) ) { return this; } if( !wxStrcmp( tag, wxT("curve") ) ) { return this; } if( !wxStrcmp( tag, wxT("point") ) ) { return this; } return NULL; } // // Write all of the curves to the XML file // void EqualizationDialog::WriteXML(XMLWriter &xmlFile) { // Start our heirarchy xmlFile.StartTag( wxT( "equalizationeffect" ) ); // Write all curves int numCurves = mCurves.GetCount(); int curve; for( curve = 0; curve < numCurves; curve++ ) { // Start a new curve xmlFile.StartTag( wxT( "curve" ) ); xmlFile.WriteAttr( wxT( "name" ), mCurves[ curve ].Name ); // Write all points int numPoints = mCurves[ curve ].points.GetCount(); int point; for( point = 0; point < numPoints; point++ ) { // Write new point xmlFile.StartTag( wxT( "point" ) ); xmlFile.WriteAttr( wxT( "f" ), mCurves[ curve ].points[ point ].Freq, 12 ); xmlFile.WriteAttr( wxT( "d" ), mCurves[ curve ].points[ point ].dB, 12 ); xmlFile.EndTag( wxT( "point" ) ); } // Terminate curve xmlFile.EndTag( wxT( "curve" ) ); } // Terminate our heirarchy xmlFile.EndTag( wxT( "equalizationeffect" ) ); } // WDR: handler implementations for EqualizationDialog // // Graphic EQ slider was adjusted // void EqualizationDialog::OnSlider(wxCommandEvent &event) { wxSliderBugfix *s = (wxSliderBugfix *)event.GetEventObject(); for (int i = 0; thirdOct[i] <= mHiFreq; ++i) { if( i == NUMBER_OF_BANDS ) break; if( s == m_sliders[i]) { int posn = m_sliders[i]->GetValue(); if( wxGetKeyState(WXK_SHIFT) ) { if( posn > m_sliders_old[i] ) m_EQVals[i] += (float).1; else if( posn < m_sliders_old[i] ) m_EQVals[i] -= .1f; } else m_EQVals[i] += (posn - m_sliders_old[i]); if( m_EQVals[i] > 20. ) m_EQVals[i] = 20.; if( m_EQVals[i] < -20. ) m_EQVals[i] = -20.; int newPosn = (int)m_EQVals[i]; m_sliders[i]->SetValue( newPosn ); m_sliders_old[i] = newPosn; #if wxUSE_TOOLTIPS wxString tip; if( thirdOct[i] < 1000.) tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] ); else tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] ); s->SetToolTip(tip); #endif break; } } GraphicEQ(mLogEnvelope); EnvelopeUpdated(); } void EqualizationDialog::OnInterp(wxCommandEvent &event) { if(mFaderOrDraw[1]->GetValue()) { GraphicEQ(mLogEnvelope); EnvelopeUpdated(); } interp = mInterpChoice->GetSelection(); } void EqualizationDialog::LayoutEQSliders() { //layout the Graphic EQ sizer here wxSize szrGSize = szrG->GetSize(); //total size we have to play with wxSize szr2Size = szr2->GetSize(); //size of the dBMax/Min sliders wxSizerItem *ruler = szr1->GetItem((size_t)1); wxSize rulerSize = ruler->GetSize(); //and the ruler wxSizerItem *EQslider = szrG->GetItem((size_t)1); wxSize EQsliderSize = EQslider->GetSize(); //size of the sliders int start, w, range, so_far; start = szr2Size.x + rulerSize.x + 12; //inc ruler & mPanel border (4+4 + 4) szrG->SetItemMinSize((size_t)0, start - EQsliderSize.x/2, -1); //set 1st spacer so that 1st slider aligned with ruler range = szrGSize.x - EQsliderSize.x/2 - start; so_far = start + EQsliderSize.x/2; double loLog = log10(mLoFreq); double hiLog = log10(mHiFreq); double denom = hiLog - loLog; for (int i = 1; thirdOct[i] <= mHiFreq; ++i) //go along the spacers { if( i == NUMBER_OF_BANDS ) break; float posn = range*(log10(thirdOct[i])-loLog)/denom; //centre of this slider, from start w = start + ((int)(posn+.5)) - EQsliderSize.x/2; //LH edge of slider, from 0 w = w - so_far; //gap needed to put it here szrG->SetItemMinSize((size_t)(i*2), w, -1); //set spacers so that sliders aligned with ruler so_far += (w + EQsliderSize.x); } RefreshRect(wxRect(szrG->GetPosition(),szrGSize)); } void EqualizationDialog::GraphicEQ(Envelope *env) { double value, dist, span, s; env->Flatten(0.); env->SetTrackLen(1.0); int n = mInterpChoice->GetSelection(); switch( n ) { case 0: // B-spline { int minF = 0; for(int i=0; i 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 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]; 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[NUMBER_OF_BANDS]; y2[0] = 0.; // u[0] = 0.; //'natural' boundary conditions for(i=1;i=0;i--) y2[i] = y2[i]*y2[i+1] + u[i]; } double EqualizationDialog::splint(double x[], double y[], int n, double y2[], double xr) { double a, b, h; static double xlast = 0.; // remember last x value requested static int k = 0; // and which interval we were in if( xr < xlast ) k = 0; // gone back to start, (or somewhere to the left) xlast = xr; while( (x[k] <= xr) && (k < n-1) ) k++; k--; h = x[k+1] - x[k]; a = ( x[k+1] - xr )/h; b = (xr - x[k])/h; return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.); } void EqualizationDialog::OnDrawRadio(wxCommandEvent &evt) { int numPoints = mLogEnvelope->GetNumberOfPoints(); double *when = new double[ numPoints ]; double *value = new double[ numPoints ]; double deltadB = 0.1; double dx, dy, dx1, dy1, err; mLogEnvelope->GetPoints( when, value, numPoints ); // set 'custom' as the selected curve EnvelopeUpdated(); bool flag = true; while (flag) { flag = false; int numDeleted = 0; mLogEnvelope->GetPoints( when, value, numPoints ); for(int j=0;jDelete(j+1); numPoints--; numDeleted++; flag = true; } } } if(mLinFreq->IsChecked()) { EnvLogToLin(); mPanel->mEnvelope = mLinEnvelope; freqRuler->ruler.SetLog(false); freqRuler->ruler.SetRange(0, mHiFreq); } szrV->Show(szrG,false); szrH->Show(szrI,false); szrH->Show(szrL,true); Layout(); mPanel->RecalcRequired = true; // it may have changed slightly due to the deletion of points mPanel->Refresh( false ); drawMode = true; } void EqualizationDialog::OnSliderRadio(wxCommandEvent &evt) { UpdateGraphic(); } void EqualizationDialog::UpdateGraphic() { double loLog = log10(mLoFreq); double hiLog = log10(mHiFreq); double denom = hiLog - loLog; if(mLinFreq->IsChecked()) //going from lin to log freq scale { // add some extra points to the linear envelope for the graphic to follow double step = pow(2., 1./12.); // twelve steps per octave double when,value; for(double freq=10.; freqGetValue(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; iGetValue(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; iSetValue(lrint(m_EQVals[i])); //actually set slider positions m_sliders_old[i] = m_sliders[i]->GetValue(); #if wxUSE_TOOLTIPS wxString tip; if( thirdOct[i] < 1000.) tip.Printf( wxT("%dHz\n%.1fdB"), (int)thirdOct[i], m_EQVals[i] ); else tip.Printf( wxT("%gkHz\n%.1fdB"), thirdOct[i]/1000., m_EQVals[i] ); m_sliders[i]->SetToolTip(tip); #endif } szrV->Show(szrG,true); // eq sliders szrH->Show(szrI,true); // interpolation choice szrH->Show(szrL,false); // linear freq checkbox Layout(); // Make all sizers get resized first LayoutEQSliders(); // Then layout sliders Layout(); // And layout again to resize dialog wxSize wsz = GetSize(); wxSize ssz = szrV->GetSize(); if (ssz.x > wsz.x || ssz.y > wsz.y) { Fit(); } GraphicEQ(mLogEnvelope); drawMode = false; } void EqualizationDialog::OnLinFreq(wxCommandEvent &evt) { if(mLinFreq->IsChecked()) //going from log to lin freq scale { freqRuler->ruler.SetLog(false); freqRuler->ruler.SetRange(0, mHiFreq); EnvLogToLin(); mPanel->mEnvelope = mLinEnvelope; linCheck = true; } else //going from lin to log freq scale { freqRuler->ruler.SetLog(true); freqRuler->ruler.SetRange(mLoFreq, mHiFreq); EnvLinToLog(); mPanel->mEnvelope = mLogEnvelope; linCheck = false; } CalcFilter(); freqRuler->Refresh(false); mPanel->Refresh(false); } void EqualizationDialog::EnvLogToLin(void) { int numPoints = mLogEnvelope->GetNumberOfPoints(); 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(); 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; for( int i=0; i < numPoints; i++) mLogEnvelope->Insert((log10(when[i]*mHiFreq)-loLog)/denom , value[i]); mLogEnvelope->Move(1., value[numPoints-1]); delete [] when; delete [] value; } void EqualizationDialog::ErrMin(void) { double vals[NUM_PTS]; int i; double error = 0.0; double oldError = 0.0; double m_EQValsOld = 0.0; double correction = 1.6; bool flag; int j=0; Envelope *testEnvelope; testEnvelope = new Envelope(); testEnvelope->SetInterpolateDB(false); testEnvelope->Mirror(false); testEnvelope->Flatten(0.); testEnvelope->SetTrackLen(1.0); testEnvelope->CopyFrom(mLogEnvelope, 0.0, 1.0); for(i=0; i < NUM_PTS; i++) vals[i] = testEnvelope->GetValue(whens[i]); // Do error minimisation error = 0.; GraphicEQ(testEnvelope); for(i=0; i < NUM_PTS; i++) //calc initial error { double err = vals[i] - testEnvelope->GetValue(whens[i]); error += err*err; } oldError = error; while( j < bandsInUse*12 ) //loop over the sliders a number of times { i = j%bandsInUse; //use this slider if( (j > 0) & (i == 0) ) // if we've come back to the first slider again... { if( correction > 0 ) correction = -correction; //go down else correction = -correction/2.; //go up half as much } flag = true; // check if we've hit the slider limit do { oldError = error; m_EQValsOld = m_EQVals[i]; m_EQVals[i] += correction; //move fader value if( m_EQVals[i] > 20. ) { m_EQVals[i] = 20.; flag = false; } if( m_EQVals[i] < -20. ) { m_EQVals[i] = -20.; flag = false; } GraphicEQ(testEnvelope); //calculate envelope error = 0.; for(int k=0; k < NUM_PTS; k++) //calculate error { double err = vals[k] - testEnvelope->GetValue(whens[k]); error += err*err; } } while( (error < oldError) & flag ); if( error > oldError ) { m_EQVals[i] = m_EQValsOld; //last one didn't work error = oldError; } else oldError = error; if( error < .0025 * bandsInUse) break; // close enuff j++; //try next slider } if( error > .0025 * bandsInUse ) // not within 0.05dB on each slider, on average { Select( (int) mCurves.GetCount()-1 ); EnvelopeUpdated(testEnvelope); } delete testEnvelope; testEnvelope = NULL; } void EqualizationDialog::OnSliderM(wxCommandEvent &event) { TransferDataFromWindow(); mPanel->RecalcRequired = true; } void EqualizationDialog::OnSliderDBMIN(wxCommandEvent &event) { TransferDataFromWindow(); } void EqualizationDialog::OnSliderDBMAX(wxCommandEvent &event) { TransferDataFromWindow(); } // // New curve was selected // void EqualizationDialog::OnCurve(wxCommandEvent &event) { // Select new curve #if wxCHECK_VERSION(2, 6, 2) && !defined(__WXX11__) setCurve( mLogEnvelope, mCurve->GetCurrentSelection() ); #else setCurve( mLogEnvelope, mCurve->GetSelection() ); #endif if( mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked() ) EnvLogToLin(); if( !drawMode ) UpdateGraphic(); } // // User wants to save a new curve // void EqualizationDialog::OnSaveAs(wxCommandEvent &event) { wxString name; int numCurves = mCurves.GetCount(); int curve; bool overWrite = false; // build the dialog wxTextEntryDialog dlg( this, _("Enter the desired name of the curve"), _("Save As...") ); // Setup list of characters that aren't allowed wxArrayString exclude; exclude.Add( wxT("<") ); exclude.Add( wxT(">") ); exclude.Add( wxT("'") ); exclude.Add( wxT("\"") ); // And tell the validator about them dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST ); wxTextValidator *tv = dlg.GetTextValidator(); tv->SetExcludes( exclude ); // Prompt the user until a valid name is enter or cancelled while( true ) { // Show the dialog and bail if the user cancels if( dlg.ShowModal() == wxID_CANCEL ) { return; } // Extract the name from the dialog name = dlg.GetValue(); // Search list of curves for a duplicate name bool bad = false; for( curve = 0; curve < numCurves; curve++ ) { if( name == mCurves[ curve ].Name ) { int answer = wxMessageBox( wxT("Overwrite existing curve?"), wxT("Curve exists"), wxYES_NO ); if (answer == wxNO) { bad = true; break; } else { overWrite = true; break; } } } if( name == wxT("") || name == wxT("custom") ) bad = true; // An unacceptable duplicate wasn't found so break out of loop if( !bad ) { break; } } if( overWrite == false) { // Create a new entry mCurves.Add( EQCurve( wxT("custom") ) ); // Copy over the points mCurves[ numCurves ].points = mCurves[ numCurves - 1 ].points; // Give the original custom entry the new name mCurves[ numCurves - 1 ].Name = name; // Recreate the choice control and layout everything again CreateChoice(); Layout(); // Select the newly name curve Select( numCurves - 1 ); } else { // Overwrite curve from custom mCurves[ curve ].points = mCurves[ numCurves - 1 ].points; // Select the overwritten curve Select( curve ); } // Save the curves to file - enables the user to set up curves without applying them SaveCurves(); } // // User wants to delete a curve // void EqualizationDialog::OnDelete(wxCommandEvent &event) { // Get the one to be deleted int sel = mCurve->GetSelection(); // Create the prompt wxString quest; quest = wxString(_("Delete")) + wxT(" ") + mCurves[ sel ].Name + wxT("?"); // Ask for confirmation int ans = wxMessageBox( quest, _("Confirm Deletion"), wxYES_NO | wxCENTRE, this ); // Denied, so bail if( ans == wxNO ) { return; } // Remove the curve from the array and choice control mCurves.RemoveAt( sel ); mCurve->Delete( sel ); // Save the curves to file - enables the user to delete curves without applying anything SaveCurves(); // set 'custom' as the selected curve Select( (int) mCurves.GetCount()-1 ); // Reselect setCurve( mLogEnvelope, mCurves.GetCount()-1 ); if( mFaderOrDraw[0]->GetValue() & mLinFreq->IsChecked() ) EnvLogToLin(); } void EqualizationDialog::OnClear(wxCommandEvent &event) { mLogEnvelope->Flatten(0.); mLogEnvelope->SetTrackLen(1.0); mLinEnvelope->Flatten(0.); mLinEnvelope->SetTrackLen(1.0); EnvelopeUpdated(); 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.; } } } void EqualizationDialog::OnErase(wxEraseEvent &event) { // Ignore it } void EqualizationDialog::OnPaint(wxPaintEvent &event) { wxPaintDC dc(this); #if defined(__WXGTK__) dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE))); #endif dc.Clear(); } void EqualizationDialog::OnSize(wxSizeEvent &event) { Layout(); if (mFaderOrDraw[1]->GetValue()) { LayoutEQSliders(); } event.Skip(); } void EqualizationDialog::OnPreview(wxCommandEvent &event) { TransferDataFromWindow(); m_pEffect->Preview(); //v Restore previous values? } void EqualizationDialog::OnCancel(wxCommandEvent &event) { if(mLogEnvelope) delete mLogEnvelope; mLogEnvelope = NULL; if(mLinEnvelope) delete mLinEnvelope; mLinEnvelope = NULL; mPanel = NULL; EndModal(false); } void EqualizationDialog::OnOk(wxCommandEvent &event) { TransferDataFromWindow(); if( Validate() ) { // Update custom curve (so it's there for next time) //(done in a hurry, may not be the neatest -MJS) if( (mDirty == true) & (drawMode == true) ) { 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;jvalue[i+1]-.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(); if(mLogEnvelope) delete mLogEnvelope; mLogEnvelope = NULL; if(mLinEnvelope) delete mLinEnvelope; mLinEnvelope = NULL; mPanel = NULL; EndModal(true); } else { event.Skip(); } } #if wxUSE_ACCESSIBILITY SliderAx::SliderAx( wxWindow * window, wxString fmt ): wxWindowAccessible( window ) { mParent = window; mFmt = fmt; } SliderAx::~SliderAx() { } // Retrieves the address of an IDispatch interface for the specified child. // All objects must support this property. wxAccStatus SliderAx::GetChild( int childId, wxAccessible** child ) { if( childId == wxACC_SELF ) { *child = this; } else { *child = NULL; } return wxACC_OK; } // Gets the number of children. wxAccStatus SliderAx::GetChildCount(int* childCount) { *childCount = 3; return wxACC_OK; } // Gets the default action for this object (0) or > 0 (the action for a child). // Return wxACC_OK even if there is no action. actionName is the action, or the empty // string if there is no action. // The retrieved string describes the action that is performed on an object, // not what the object does as a result. For example, a toolbar button that prints // a document has a default action of "Press" rather than "Prints the current document." wxAccStatus SliderAx::GetDefaultAction( int childId, wxString *actionName ) { actionName->Clear(); return wxACC_OK; } // Returns the description for this object or a child. wxAccStatus SliderAx::GetDescription( int childId, wxString *description ) { description->Clear(); return wxACC_OK; } // Gets the window with the keyboard focus. // If childId is 0 and child is NULL, no object in // this subhierarchy has the focus. // If this object has the focus, child should be 'this'. wxAccStatus SliderAx::GetFocus(int* childId, wxAccessible** child) { *childId = 0; *child = this; return wxACC_OK; } // Returns help text for this object or a child, similar to tooltip text. wxAccStatus SliderAx::GetHelpText( int childId, wxString *helpText ) { helpText->Clear(); return wxACC_OK; } // Returns the keyboard shortcut for this object or child. // Return e.g. ALT+K wxAccStatus SliderAx::GetKeyboardShortcut( int childId, wxString *shortcut ) { shortcut->Clear(); return wxACC_OK; } // Returns the rectangle for this object (id = 0) or a child element (id > 0). // rect is in screen coordinates. wxAccStatus SliderAx::GetLocation( wxRect& rect, int elementId ) { wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); rect = s->GetRect(); rect.SetPosition( s->GetParent()->ClientToScreen( rect.GetPosition() ) ); return wxACC_OK; } // Gets the name of the specified object. wxAccStatus SliderAx::GetName(int childId, wxString* name) { wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); *name = s->GetName(); return wxACC_OK; } // Returns a role constant. wxAccStatus SliderAx::GetRole(int childId, wxAccRole* role) { switch( childId ) { case 0: *role = wxROLE_SYSTEM_SLIDER; break; case 1: case 3: *role = wxROLE_SYSTEM_PUSHBUTTON; break; case 2: *role = wxROLE_SYSTEM_INDICATOR; break; } return wxACC_OK; } // Gets a variant representing the selected children // of this object. // Acceptable values: // - a null variant (IsNull() returns TRUE) // - a list variant (GetType() == wxT("list")) // - an integer representing the selected child element, // or 0 if this object is selected (GetType() == wxT("long")) // - a "void*" pointer to a wxAccessible child object wxAccStatus SliderAx::GetSelections( wxVariant *selections ) { return wxACC_NOT_IMPLEMENTED; } // Returns a state constant. wxAccStatus SliderAx::GetState(int childId, long* state) { wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); switch( childId ) { case 0: *state = wxACC_STATE_SYSTEM_FOCUSABLE; break; case 1: if( s->GetValue() == s->GetMin() ) { *state = wxACC_STATE_SYSTEM_INVISIBLE; } break; case 3: if( s->GetValue() == s->GetMax() ) { *state = wxACC_STATE_SYSTEM_INVISIBLE; } break; } // Do not use mSliderIsFocused is not set until after this method // is called. *state |= ( s == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0 ); return wxACC_OK; } // Returns a localized string representing the value for the object // or child. wxAccStatus SliderAx::GetValue(int childId, wxString* strValue) { wxSliderBugfix *s = wxDynamicCast( GetWindow(), wxSliderBugfix ); if( childId == 0 ) { strValue->Printf( mFmt, s->GetValue() ); return wxACC_OK; } return wxACC_NOT_SUPPORTED; } #endif // Indentation settings for Vim and Emacs and unique identifier for Arch, a // version control system. Please do not modify past this point. // // Local Variables: // c-basic-offset: 3 // indent-tabs-mode: nil // End: // // vim: et sts=3 sw=3 // arch-tag: 65b35bfa-632c-46fe-9170-840a158b3c97