/********************************************************************** Audacity: A Digital Audio Editor Effect/ScienFilter.cpp Norm C Mitch Golden Vaughan Johnson (Preview) *******************************************************************//** \file ScienFilter.cpp \brief Implements EffectScienFilter, ScienFilterDialog, ScienFilterPanel. *//****************************************************************//** \class EffectScienFilter \brief An Effect. Performs IIR filtering that emulates analog filters, specifically Butterworth, Chebyshev Type I and Type II. Highpass and lowpass filters are supported, as are filter orders from 1 to 10. The filter is applied using biquads *//****************************************************************//** \class ScienFilterDialog \brief Dialog used with EffectScienFilter *//****************************************************************//** \class ScienFilterPanel \brief ScienFilterPanel is used with ScienFilterDialog and controls a graph for EffectScienFilter. *//*******************************************************************/ #include "../Audacity.h" #include "ScienFilter.h" #include "../AColor.h" #include "../ShuttleGui.h" #include "../PlatformCompatibility.h" //#include "../Envelope.h" #include "../Prefs.h" #include "../Project.h" #include "../WaveTrack.h" #include "../widgets/Ruler.h" #include "../Theme.h" #include "../AllThemeResources.h" #include "../WaveTrack.h" #include "float_cast.h" #include //#include #include #include #include #include #include #include //#include #include #include #include #include #include //#include #if wxUSE_TOOLTIPS #include #endif #include #include #include #define PI 3.1415926535 #define square(a) ((a)*(a)) #define NUM_INTERP_CHOICES 3 static wxString interpChoiceStrings[NUM_INTERP_CHOICES]; #ifndef __min #define __min(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef __max #define __max(a,b) ((a) > (b) ? (a) : (b)) #endif // Local functions void EffectScienFilter::ReadPrefs() { double dTemp; gPrefs->Read(wxT("/SciFilter/Order"), &mOrder, 1); mOrder = __max (1, mOrder); mOrder = __min (MAX_FILTER_ORDER, mOrder); gPrefs->Read(wxT("/SciFilter/FilterType"), &mFilterType, 0); mFilterType = __max (0, mFilterType); mFilterType = __min (2, mFilterType); gPrefs->Read(wxT("/SciFilter/FilterSubtype"), &mFilterSubtype, 0); mFilterSubtype = __max (0, mFilterSubtype); mFilterSubtype = __min (1, mFilterSubtype); gPrefs->Read(wxT("/SciFilter/Cutoff"), &dTemp, 1000.0); mCutoff = (float)dTemp; mCutoff = __max (1, mCutoff); mCutoff = __min (100000, mCutoff); gPrefs->Read(wxT("/SciFilter/Ripple"), &dTemp, 1.0); mRipple = dTemp; mRipple = __max (0, mRipple); mRipple = __min (100, mRipple); gPrefs->Read(wxT("/SciFilter/StopbandRipple"), &dTemp, 30.0); mStopbandRipple = dTemp; mStopbandRipple = __max (0, mStopbandRipple); mStopbandRipple = __min (100, mStopbandRipple); } EffectScienFilter::EffectScienFilter() { ReadPrefs(); mPrompting = false; } EffectScienFilter::~EffectScienFilter() { } bool EffectScienFilter::Init() { return(true); } bool EffectScienFilter::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.); ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, mParent, -1, _("Scientific Filter")); dlog.dBMin = mdBMin; dlog.dBMax = mdBMax; dlog.Order = mOrder; dlog.Cutoff = mCutoff; dlog.FilterType = mFilterType; dlog.FilterSubtype = mFilterSubtype; dlog.Ripple = mRipple; dlog.StopbandRipple = mStopbandRipple; dlog.CentreOnParent(); mPrompting = true; // true when previewing, false in batch dlog.ShowModal(); mPrompting = false; if (!dlog.GetReturnCode()) return false; mdBMin = dlog.dBMin; mdBMax = dlog.dBMax; mOrder = dlog.Order; mCutoff = dlog.Cutoff; mFilterType = dlog.FilterType; mFilterSubtype = dlog.FilterSubtype; mRipple = dlog.Ripple; mStopbandRipple = dlog.StopbandRipple; if (!mEditingBatchParams) { // Save preferences gPrefs->Write(wxT("/SciFilter/Order"), mOrder); gPrefs->Write(wxT("/SciFilter/FilterType"), mFilterType); gPrefs->Write(wxT("/SciFilter/FilterSubtype"), mFilterSubtype); gPrefs->Write(wxT("/SciFilter/Cutoff"), mCutoff); gPrefs->Write(wxT("/SciFilter/Ripple"), mRipple); gPrefs->Write(wxT("/SciFilter/StopbandRipple"), mStopbandRipple); } return true; } bool EffectScienFilter::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.); ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, NULL, -1, _("ScienFilter")); dlog.dBMin = mdBMin; dlog.dBMax = mdBMax; dlog.Order = mOrder; dlog.Cutoff = mCutoff; dlog.FilterType = mFilterType; dlog.FilterSubtype = mFilterSubtype; dlog.Ripple = mRipple; dlog.CalcFilter(this); return true; } bool EffectScienFilter::TransferParameters( Shuttle & shuttle ) { // if shuttle.mbStoreInClient is true, read prefs ScienFilter/FilterType (etc.) string and put into mFilterType (etc.) // else put mFilterType (etc.) into string form and write prefs shuttle.TransferInt(wxT("FilterType"),mFilterType,0); shuttle.TransferInt(wxT("FilterSubtype"),mFilterSubtype,0); // etc. shuttle.TransferInt(wxT("Order"),mOrder,2); shuttle.TransferFloat(wxT("Cutoff"),mCutoff,1000); shuttle.TransferFloat(wxT("PassbandRipple"),mRipple,1); shuttle.TransferFloat(wxT("StopbandRipple"),mStopbandRipple,30); if(!mPrompting) DontPromptUser(); // not previewing, ie batch mode or initial setup return true; } bool EffectScienFilter::Process() { this->CopyInputTracks(); // Set up mOutputTracks. bool bGoodResult = true; SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); WaveTrack *track = (WaveTrack *) iter.First(); int count = 0; while (track) { double trackStart = track->GetStartTime(); double trackEnd = track->GetEndTime(); double t0 = mT0 < trackStart? trackStart: mT0; double t1 = mT1 > trackEnd? trackEnd: mT1; if (t1 > t0) { sampleCount start = track->TimeToLongSamples(t0); sampleCount end = track->TimeToLongSamples(t1); sampleCount len = (sampleCount)(end - start); if (!ProcessOne(count, track, start, len)) { bGoodResult = false; break; } } track = (WaveTrack *) iter.Next(); count++; } this->ReplaceProcessedTracks(bGoodResult); return bGoodResult; } bool EffectScienFilter::ProcessOne(int count, WaveTrack * t, sampleCount start, sampleCount len) { // Create a new WaveTrack to hold all of the output AudacityProject *p = GetActiveProject(); WaveTrack *output = p->GetTrackFactory()->NewWaveTrack(floatSample, t->GetRate()); sampleCount s = start; sampleCount idealBlockLen = t->GetMaxBlockSize(); float *buffer = new float[idealBlockLen]; sampleCount originalLen = len; TrackProgress(count, 0.0); bool bLoopSuccess = true; for (int iPair = 0; iPair < (mOrder+1)/2; iPair++) mpBiquad [iPair]->fPrevIn = mpBiquad [iPair]->fPrevPrevIn = mpBiquad [iPair]->fPrevOut = mpBiquad [iPair]->fPrevPrevOut = 0; while(len) { sampleCount block = idealBlockLen; if (block > len) block = len; t->Get((samplePtr)buffer, floatSample, s, block); for (int iPair = 0; iPair < (mOrder+1)/2; iPair++) { mpBiquad[iPair]->pfIn = buffer; mpBiquad[iPair]->pfOut = buffer; Biquad_Process (mpBiquad[iPair], block); } output->Append ((samplePtr)buffer, floatSample, block); len -= block; s += block; if (TrackProgress (count, (s-start)/(double)originalLen)) { bLoopSuccess = false; break; } } if (bLoopSuccess) { output->Flush(); // Now move the appropriate bit of the output back to the track float *bigBuffer = new float[originalLen]; output->Get((samplePtr)bigBuffer, floatSample, 0, originalLen); t->Set((samplePtr)bigBuffer, floatSample, start, originalLen); delete[] bigBuffer; } delete[] buffer; delete output; return bLoopSuccess; } void EffectScienFilter::Filter(sampleCount WXUNUSED(len), float *WXUNUSED(buffer)) { } //---------------------------------------------------------------------------- // ScienFilterPanel //---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(ScienFilterPanel, wxPanel) EVT_PAINT(ScienFilterPanel::OnPaint) EVT_SIZE(ScienFilterPanel::OnSize) END_EVENT_TABLE() ScienFilterPanel::ScienFilterPanel( double loFreq, double hiFreq, ScienFilterDialog *parent, wxWindowID id, const wxPoint& pos, const wxSize& size): wxPanel(parent, id, pos, size) { mBitmap = NULL; mWidth = 0; mHeight = 0; mLoFreq = loFreq; mHiFreq = hiFreq; mParent = parent; } ScienFilterPanel::~ScienFilterPanel() { if (mBitmap) delete mBitmap; } void ScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt)) { Refresh( false ); } void ScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt)) { wxPaintDC dc(this); int width, height; GetSize(&width, &height); if (!mBitmap || mWidth!=width || mHeight!=height) { if (mBitmap) delete mBitmap; mWidth = width; mHeight = height; mBitmap = new wxBitmap(mWidth, mHeight); } wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); wxMemoryDC memDC; memDC.SelectObject(*mBitmap); wxRect bkgndRect; bkgndRect.x = 0; bkgndRect.y = 0; bkgndRect.width = mWidth; bkgndRect.height = mHeight; memDC.SetBrush(bkgndBrush); memDC.SetPen(*wxTRANSPARENT_PEN); memDC.DrawRectangle(bkgndRect); bkgndRect.y = mHeight; memDC.DrawRectangle(bkgndRect); wxRect border; border.x = 0; border.y = 0; border.width = mWidth; border.height = mHeight; memDC.SetBrush(*wxWHITE_BRUSH); memDC.SetPen(*wxBLACK_PEN); memDC.DrawRectangle(border); mEnvRect = border; mEnvRect.Deflate(2, 2); // Pure blue x-axis line memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID)); int center = (int) (mEnvRect.height * dBMax/(dBMax-dBMin) + .5); AColor::Line(memDC, mEnvRect.GetLeft(), mEnvRect.y + center, mEnvRect.GetRight(), mEnvRect.y + center); //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 ), 3, wxSOLID)); double scale = (double)mEnvRect.height/(dBMax-dBMin); // pixels per dB double yF; // gain at this freq double loLog = log10(mLoFreq); double step = log10(mHiFreq) - loLog; step /= ((double)mEnvRect.width-1.); double freq; // actual freq corresponding to x position int x, y, xlast = 0, ylast = 0; for(int i=0; iFilterMagnAtFreq (freq); yF = 20*log10(yF); 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 && (y < mEnvRect.height-1 || ylast < mEnvRect.y + mEnvRect.height-1)) { AColor::Line (memDC, xlast, ylast, x, mEnvRect.y + y); } xlast = x; ylast = mEnvRect.y + y; } memDC.SetPen(*wxBLACK_PEN); mParent->freqRuler->ruler.DrawGrid(memDC, mEnvRect.height+2, true, true, 0, 1); mParent->dBRuler->ruler.DrawGrid(memDC, mEnvRect.width+2, true, true, 1, 2); dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE); } // WDR: class implementations //---------------------------------------------------------------------------- // ScienFilterDialog //---------------------------------------------------------------------------- // WDR: event table for ScienFilterDialog BEGIN_EVENT_TABLE(ScienFilterDialog,wxDialog) EVT_SIZE( ScienFilterDialog::OnSize ) EVT_PAINT( ScienFilterDialog::OnPaint ) EVT_ERASE_BACKGROUND( ScienFilterDialog::OnErase ) EVT_SLIDER( ID_DBMAX, ScienFilterDialog::OnSliderDBMAX ) EVT_SLIDER( ID_DBMIN, ScienFilterDialog::OnSliderDBMIN ) EVT_CHOICE( ID_FILTER_ORDER, ScienFilterDialog::OnOrder) EVT_CHOICE( ID_FILTER_TYPE, ScienFilterDialog::OnFilterType) EVT_CHOICE( ID_FILTER_SUBTYPE, ScienFilterDialog::OnFilterSubtype) EVT_TEXT( ID_CUTOFF, ScienFilterDialog::OnCutoff) EVT_TEXT( ID_RIPPLE, ScienFilterDialog::OnRipple) EVT_TEXT( ID_STOPBAND_RIPPLE, ScienFilterDialog::OnStopbandRipple) EVT_BUTTON( ID_EFFECT_PREVIEW, ScienFilterDialog::OnPreview ) EVT_BUTTON( wxID_OK, ScienFilterDialog::OnOk ) EVT_BUTTON( wxID_CANCEL, ScienFilterDialog::OnCancel ) END_EVENT_TABLE() ScienFilterDialog::ScienFilterDialog(EffectScienFilter * effect, double loFreq, double hiFreq, 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; dBMin = -30.; dBMax = 30; #if wxUSE_TOOLTIPS wxToolTip::Enable(true); #endif mLoFreq = loFreq; mNyquist = hiFreq; memset (effect->mpBiquad, 0, sizeof(effect->mpBiquad)); for (int i = 0; i < MAX_FILTER_ORDER/2; i++) { effect->mpBiquad[i] = (BiquadStruct*)calloc (sizeof (BiquadStruct), 1); effect->mpBiquad[i]->fNumerCoeffs [0] = 1.0; // straight-through } // Create the dialog MakeScienFilterDialog(); } ScienFilterDialog::~ScienFilterDialog() { } // // Create the ScienFilter dialog // void ScienFilterDialog::MakeScienFilterDialog() { mCutoffCtl = NULL; mRippleCtl = NULL; mStopbandRippleCtl = NULL; // Create the base sizer szrV = new wxBoxSizer( wxVERTICAL ); // ------------------------------------------------------------------- // ROW 1: Freq response panel and sliders for vertical scale // ------------------------------------------------------------------- szr1 = new wxFlexGridSizer( 3, 0, 0 ); szr1->AddGrowableCol( 2, 1 ); szr1->AddGrowableRow( 0, 1 ); szr1->SetFlexibleDirection( wxBOTH ); szr2 = new wxBoxSizer( wxVERTICAL ); dBMaxSlider = new wxSlider(this, ID_DBMAX, 10, 0, 20, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); szr2->Add( dBMaxSlider, 1, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 4 ); dBMinSlider = new wxSlider(this, ID_DBMIN, -10, -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(30.0, -90.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 ); wxSize size; size.Set (400, 200); mPanel = new ScienFilterPanel( mLoFreq, mNyquist, this, ID_FILTERPANEL, wxDefaultPosition, size); szr1->Add( mPanel, 1, wxEXPAND|wxALIGN_CENTRE|wxRIGHT, 4); /// 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, mNyquist); freqRuler->ruler.SetFormat(Ruler::IntFormat); freqRuler->ruler.SetUnits(_("")); 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 2 and 3: Type, Order, Ripple, Subtype, Cutoff // ------------------------------------------------------------------- szr3 = new wxFlexGridSizer (7, 0, 0); szr3->Add (new wxStaticText(this, wxID_ANY, _("Filter Type:")), wxRIGHT); mFilterTypeCtl = new wxChoice (this, ID_FILTER_TYPE); mFilterTypeCtl->Append (_("Butterworth")); mFilterTypeCtl->Append (_("Chebyshev Type I")); mFilterTypeCtl->Append (_("Chebyshev Type II")); szr3->Add (mFilterTypeCtl); szr3->Add( new wxStaticText(this, wxID_ANY, _(" Order:")), wxRIGHT ); mFilterOrderCtl = new wxChoice (this, ID_FILTER_ORDER); mFilterOrderCtl->Append (_("1")); mFilterOrderCtl->Append (_("2")); mFilterOrderCtl->Append (_("3")); mFilterOrderCtl->Append (_("4")); mFilterOrderCtl->Append (_("5")); mFilterOrderCtl->Append (_("6")); mFilterOrderCtl->Append (_("7")); mFilterOrderCtl->Append (_("8")); mFilterOrderCtl->Append (_("9")); mFilterOrderCtl->Append (_("10")); szr3->Add (mFilterOrderCtl); szr3->Add (new wxStaticText(this, wxID_ANY, _("Passband Ripple:")), wxSizerFlags().Right()); wxSize Size(wxDefaultSize); Size.SetWidth (40); mRippleCtl = new wxTextCtrl (this, ID_RIPPLE, _("0.0"), wxDefaultPosition, Size); szr3->Add (mRippleCtl, 0 ); szr3->Add( new wxStaticText(this, wxID_ANY, _("dB")), 0 ); szr3->Add( new wxStaticText(this, wxID_ANY, _("Subtype:")), wxRIGHT ); mFilterSubTypeCtl = new wxChoice (this, ID_FILTER_SUBTYPE); mFilterSubTypeCtl->Append (_("Lowpass")); mFilterSubTypeCtl->Append (_("Highpass")); szr3->Add (mFilterSubTypeCtl); szr3->Add( new wxStaticText(this, wxID_ANY, _("Cutoff:")), wxRIGHT ); Size.SetWidth (50); mCutoffCtl = new wxTextCtrl (this, ID_CUTOFF, _("0.0"), wxDefaultPosition, Size); szr3->Add (mCutoffCtl, 0 ); szr3->Add( new wxStaticText(this, wxID_ANY, _("Hz Stopband Ripple:")), 0 ); Size.SetWidth (40); mStopbandRippleCtl = new wxTextCtrl (this, ID_STOPBAND_RIPPLE, _("0.0"), wxDefaultPosition, Size); szr3->Add (mStopbandRippleCtl, 0 ); szr3->Add( new wxStaticText(this, wxID_ANY, _("dB")), 0 ); // ------------------------------------------------------------------- // ROW 4: Subtype, Cutoff // ------------------------------------------------------------------- szrV->Add( szr3, 0, wxALIGN_CENTER | wxALL, 4 ); // ------------------------------------------------------------------- // ROW 5: Preview, OK, & Cancel buttons // ------------------------------------------------------------------- szrV->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND); // ------------------------------------------------------------------- // Display now // ------------------------------------------------------------------- SetAutoLayout(false); SetSizerAndFit( szrV ); SetSizeHints(GetSize()); return; } // // Validate data // bool ScienFilterDialog::Validate() { // In this case I don't think there's anything the user could have screwed up return true; } // // Populate the window with relevant variables // bool ScienFilterDialog::TransferDataToWindow() { dBMinSlider->SetValue((int)dBMin); dBMin = 0; // force refresh in TransferGraphLimitsFromWindow() dBMaxSlider->SetValue((int)dBMax); dBMax = 0; // force refresh in TransferGraphLimitsFromWindow() mFilterTypeCtl->SetSelection (FilterType); mFilterOrderCtl->SetSelection (Order - 1); mFilterSubTypeCtl->SetSelection (FilterSubtype); mCutoffCtl->SetValue (Internat::ToDisplayString(Cutoff)); mRippleCtl->SetValue (Internat::ToDisplayString(Ripple)); mStopbandRippleCtl->SetValue (Internat::ToDisplayString(StopbandRipple)); EnableDisableRippleCtl (FilterType); return TransferGraphLimitsFromWindow(); } // // Retrieve data from the window // bool ScienFilterDialog::TransferGraphLimitsFromWindow() { // 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(); freqRuler->Refresh(false); } dBRuler->Refresh(false); } mPanel->Refresh(false); return true; } bool ScienFilterDialog::CalcFilter (EffectScienFilter* effect) { TrackListOfKindIterator iter(Track::Wave, effect->mTracks); WaveTrack *t = (WaveTrack *) iter.First(); float hiFreq; if (t) hiFreq = ((float)(t->GetRate())/2.); else hiFreq = ((float)(GetActiveProject()->GetRate())/2.); // Set up the coefficients in all the biquads float fNorm = Cutoff / hiFreq; if (fNorm >= 0.9999) fNorm = 0.9999F; float fC = tan (PI * fNorm / 2); float fDCPoleDistSqr = 1.0F; float fZPoleX, fZPoleY; float fZZeroX, fZZeroY; float beta = cos (fNorm*PI); switch (FilterType) { case 0: // Butterworth if ((Order & 1) == 0) { // Even order for (int iPair = 0; iPair < Order/2; iPair++) { float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / Order); float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / Order); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1; if (FilterSubtype == 0) // LOWPASS effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2; else effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2; effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1; effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); if (FilterSubtype == 0) // LOWPASS fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); else fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist } } else { // Odd order - first do the 1st-order section float fSPoleX = -fC; float fSPoleY = 0; BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); effect->mpBiquad[0]->fNumerCoeffs [0] = 1; if (FilterSubtype == 0) // LOWPASS effect->mpBiquad[0]->fNumerCoeffs [1] = 1; else effect->mpBiquad[0]->fNumerCoeffs [1] = -1; effect->mpBiquad[0]->fNumerCoeffs [2] = 0; effect->mpBiquad[0]->fDenomCoeffs [0] = -fZPoleX; effect->mpBiquad[0]->fDenomCoeffs [1] = 0; if (FilterSubtype == 0) // LOWPASS fDCPoleDistSqr = 1 - fZPoleX; else fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist for (int iPair = 1; iPair <= Order/2; iPair++) { float fSPoleX = fC * cos (PI - iPair * PI / Order); float fSPoleY = fC * sin (PI - iPair * PI / Order); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); effect->mpBiquad[iPair]->fNumerCoeffs [0] = 1; if (FilterSubtype == 0) // LOWPASS effect->mpBiquad[iPair]->fNumerCoeffs [1] = 2; else effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2; effect->mpBiquad[iPair]->fNumerCoeffs [2] = 1; effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); if (FilterSubtype == 0) // LOWPASS fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); else fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist } } effect->mpBiquad[0]->fNumerCoeffs [0] *= fDCPoleDistSqr / (1 << Order); // mult by DC dist from poles, divide by dist from zeroes effect->mpBiquad[0]->fNumerCoeffs [1] *= fDCPoleDistSqr / (1 << Order); effect->mpBiquad[0]->fNumerCoeffs [2] *= fDCPoleDistSqr / (1 << Order); break; case 1: // Chebyshev Type 1 double eps; eps = sqrt (pow (10.0, __max(0.001, Ripple) / 10.0) - 1); double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order; // Assume even order to start for (int iPair = 0; iPair < Order/2; iPair++) { float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)); float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); if (FilterSubtype == 0) // LOWPASS { fZZeroX = -1; fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist } else { // Highpass - do the digital LP->HP transform on the poles and zeroes ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); fZZeroX = 1; fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist } effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr; effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; effect->mpBiquad[iPair]->fNumerCoeffs [2] = fDCPoleDistSqr; effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); } if ((Order & 1) == 0) { float fTemp = pow (10.0, -__max(0.001, Ripple) / 20.0); // at DC the response is down R dB (for even-order) effect->mpBiquad[0]->fNumerCoeffs [0] *= fTemp; effect->mpBiquad[0]->fNumerCoeffs [1] *= fTemp; effect->mpBiquad[0]->fNumerCoeffs [2] *= fTemp; } else { // Odd order - now do the 1st-order section float fSPoleX = -fC * sinh (a); float fSPoleY = 0; BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); if (FilterSubtype == 0) // LOWPASS { fZZeroX = -1; fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); fDCPoleDistSqr /= 2; // dist from zero at Nyquist } else { // Highpass - do the digital LP->HP transform on the poles and zeroes ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); fZZeroX = 1; fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist fDCPoleDistSqr /= 2; // dist from zero at Nyquist } effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [0] = fDCPoleDistSqr; effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; effect->mpBiquad[(Order-1)/2]->fNumerCoeffs [2] = 0; effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [0] = -fZPoleX; effect->mpBiquad[(Order-1)/2]->fDenomCoeffs [1] = 0; } break; case 2: // Chebyshev Type 2 float fSZeroX, fSZeroY; float fSPoleX, fSPoleY; eps = pow (10.0, -__max(0.001, StopbandRipple) / 20.0); a = log (1 / eps + sqrt(1 / square(eps) + 1)) / Order; // Assume even order for (int iPair = 0; iPair < Order/2; iPair++) { ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)), cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)), &fSPoleX, &fSPoleY); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); fSZeroX = 0; fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * Order)); BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY); if (FilterSubtype == 0) // LOWPASS { fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY); } else { // Highpass - do the digital LP->HP transform on the poles and zeroes ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY); fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY); } effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr; effect->mpBiquad[iPair]->fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; effect->mpBiquad[iPair]->fNumerCoeffs [2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr; effect->mpBiquad[iPair]->fDenomCoeffs [0] = -2 * fZPoleX; effect->mpBiquad[iPair]->fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); } // Now, if it's odd order, we have one more to do if (Order & 1) { int iPair = (Order-1)/2; // we'll do it as a biquad, but it's just first-order ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * Order)), cosh (a) * cos ((2*iPair + 1) * PI / (2 * Order)), &fSPoleX, &fSPoleY); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); fZZeroX = -1; // in the s-plane, the zero is at infinity fZZeroY = 0; if (FilterSubtype == 0) // LOWPASS { fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); fDCPoleDistSqr /= 2; } else { // Highpass - do the digital LP->HP transform on the poles and zeroes ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY); fZZeroX = 1; fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist fDCPoleDistSqr /= 2; } effect->mpBiquad[iPair]->fNumerCoeffs [0] = fDCPoleDistSqr; effect->mpBiquad[iPair]->fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; effect->mpBiquad[iPair]->fNumerCoeffs [2] = 0; effect->mpBiquad[iPair]->fDenomCoeffs [0] = -fZPoleX; effect->mpBiquad[iPair]->fDenomCoeffs [1] = 0; } break; } effect->mOrder = Order; // ?? needed for ProcessOne to work in Preview. This probably should be done a different way, but how? return true; } static double s_fChebyCoeffs [MAX_FILTER_ORDER][MAX_FILTER_ORDER+1] = { // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial) // Coeffs are in the order 0, 1, 2...9 {0, 1}, // order 1 {-1, 0, 2}, // order 2 etc. {0, -3, 0, 4}, {1, 0, -8, 0, 8}, {0, 5, 0, -20, 0, 16}, {-1, 0, 18, 0, -48, 0, 32}, {0, -7, 0, 56, 0, -112, 0, 64}, {1, 0, -32, 0, 160, 0, -256, 0, 128}, {0, 9, 0, -120, 0, 432, 0, -576, 0, 256}, {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512} }; static double ChebyPoly (int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down) { // Calc cosh (Order * acosh (NormFreq)); double x = 1; double fSum = 0; wxASSERT (Order > 0 && Order <= MAX_FILTER_ORDER); for (int i = 0; i <= Order; i++) { fSum += s_fChebyCoeffs [Order-1][i] * x; x *= NormFreq; } return fSum; } float ScienFilterDialog::FilterMagnAtFreq (float Freq) { float Magn; if (Freq >= mNyquist) Freq = mNyquist - 1; // prevent tan(PI/2) float FreqWarped = tan (PI * Freq/(2*mNyquist)); if (Cutoff >= mNyquist) Cutoff = mNyquist - 1; float CutoffWarped = tan (PI * Cutoff/(2*mNyquist)); float fOverflowThresh = pow (10.0, 12.0 / (2*Order)); // once we exceed 10^12 there's not much to be gained and overflow could happen switch (FilterType) { case 0: // Butterworth default: switch (FilterSubtype) { case 0: // lowpass default: if (FreqWarped/CutoffWarped > fOverflowThresh) // prevent pow() overflow Magn = 0; else Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*Order))); break; case 1: // highpass if (FreqWarped/CutoffWarped > fOverflowThresh) Magn = 1; else Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*Order) / (1 + pow (FreqWarped/CutoffWarped, 2*Order))); break; } break; case 1: // Chebyshev Type 1 double eps; eps = sqrt(pow (10.0, __max(0.001, Ripple)/10.0) - 1); switch (FilterSubtype) { case 0: // lowpass default: Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped)))); break; case 1: Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped)))); break; } break; case 2: // Chebyshev Type 2 eps = 1 / sqrt(pow (10.0, __max(0.001, StopbandRipple)/10.0) - 1); switch (FilterSubtype) { case 0: // lowpass default: Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, CutoffWarped/FreqWarped))))); break; case 1: Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(Order, FreqWarped/CutoffWarped))))); break; } break; } return Magn; } // WDR: handler implementations for ScienFilterDialog void ScienFilterDialog::OnOrder(wxCommandEvent &WXUNUSED(event)) { Order = mFilterOrderCtl->GetSelection() + 1; // 0..n-1 -> 1..n mPanel->Refresh (false); } void ScienFilterDialog::OnFilterType (wxCommandEvent &WXUNUSED(event)) { FilterType = mFilterTypeCtl->GetSelection(); EnableDisableRippleCtl (FilterType); mPanel->Refresh (false); } void ScienFilterDialog::OnFilterSubtype (wxCommandEvent &WXUNUSED(event)) { FilterSubtype = mFilterSubTypeCtl->GetSelection(); mPanel->Refresh (false); } void ScienFilterDialog::OnCutoff (wxCommandEvent &WXUNUSED(event)) { double CutoffTemp; if (mCutoffCtl) { if (mCutoffCtl->GetValue().ToDouble(&CutoffTemp)) { Cutoff = CutoffTemp; if (Cutoff >= mNyquist) { Cutoff = mNyquist - 1; // could handle Nyquist as a special case? eg. straight through if LPF mCutoffCtl->SetValue(Internat::ToDisplayString(Cutoff)); } wxButton *ok = (wxButton *) FindWindow(wxID_OK); if (Cutoff < 0.1) // 0.1 Hz min { // Disable OK button ok->Enable(0); } else ok->Enable(1); } mPanel->Refresh (false); } } void ScienFilterDialog::OnRipple (wxCommandEvent &WXUNUSED(event)) { double RippleTemp; if (mRippleCtl) { if (mRippleCtl->GetValue().ToDouble(&RippleTemp)) Ripple = RippleTemp; mPanel->Refresh (false); } } void ScienFilterDialog::OnStopbandRipple (wxCommandEvent &WXUNUSED(event)) { double RippleTemp; if (mStopbandRippleCtl) { if (mStopbandRippleCtl->GetValue().ToDouble(&RippleTemp)) StopbandRipple = RippleTemp; mPanel->Refresh (false); } } void ScienFilterDialog::OnSliderDBMIN(wxCommandEvent &WXUNUSED(event)) { TransferGraphLimitsFromWindow(); } void ScienFilterDialog::OnSliderDBMAX(wxCommandEvent &WXUNUSED(event)) { TransferGraphLimitsFromWindow(); } void ScienFilterDialog::OnErase(wxEraseEvent &WXUNUSED(event)) { // Ignore it } void ScienFilterDialog::OnPaint(wxPaintEvent &WXUNUSED(event)) { wxPaintDC dc(this); #if defined(__WXGTK__) dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE))); #endif dc.Clear(); } void ScienFilterDialog::OnSize(wxSizeEvent &event) { Layout(); event.Skip(); } void ScienFilterDialog::OnPreview(wxCommandEvent &WXUNUSED(event)) { CalcFilter (m_pEffect); m_pEffect->Preview(); } void ScienFilterDialog::Finish(bool ok) { mPanel = NULL; EndModal(ok); } void ScienFilterDialog::OnCancel(wxCommandEvent &WXUNUSED(event)) { Finish(false); } void ScienFilterDialog::OnOk(wxCommandEvent &event) { CalcFilter (m_pEffect); if( Validate() ) { Finish(true); } else { event.Skip(false); } } void ScienFilterDialog::EnableDisableRippleCtl (int FilterType) { if (FilterType == 0) // Butterworth { mRippleCtl->SetEditable (false); mStopbandRippleCtl->SetEditable (false); mRippleCtl->SetBackgroundColour (*wxLIGHT_GREY); mStopbandRippleCtl->SetBackgroundColour (*wxLIGHT_GREY); } else if (FilterType == 1) // Chebyshev Type1 { mRippleCtl->SetEditable (true); mStopbandRippleCtl->SetEditable (false); mRippleCtl->SetBackgroundColour (*wxWHITE); mStopbandRippleCtl->SetBackgroundColour (*wxLIGHT_GREY); } else // Chebyshev Type2 { mRippleCtl->SetEditable (false); mStopbandRippleCtl->SetEditable (true); mRippleCtl->SetBackgroundColour (*wxLIGHT_GREY); mStopbandRippleCtl->SetBackgroundColour (*wxWHITE); } }