From 2543ea7897ceb354715f14214add41c343868e93 Mon Sep 17 00:00:00 2001 From: "james.k.crook@gmail.com" Date: Wed, 2 Oct 2013 10:32:41 +0000 Subject: [PATCH] Added Norm C's Scientific Filter. --- src/Makefile.in | 2 + src/effects/Biquad.cpp | 51 + src/effects/Biquad.h | 18 + src/effects/LoadEffects.cpp | 2 + src/effects/ScienFilter.cpp | 1248 +++++++++++++++++++++++++ src/effects/ScienFilter.h | 341 +++++++ win/Projects/Audacity/Audacity.vcproj | 16 + 7 files changed, 1678 insertions(+) create mode 100644 src/effects/Biquad.cpp create mode 100644 src/effects/Biquad.h create mode 100644 src/effects/ScienFilter.cpp create mode 100644 src/effects/ScienFilter.h diff --git a/src/Makefile.in b/src/Makefile.in index 51024b7df..0ec5d4419 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -151,6 +151,7 @@ OBJS = \ effects/Amplify.o \ effects/AutoDuck.o \ effects/BassTreble.o \ + effects/Biquad.o \ effects/ChangePitch.o \ effects/ChangeSpeed.o \ effects/ChangeTempo.o \ @@ -173,6 +174,7 @@ OBJS = \ effects/Repeat.o \ effects/Reverb.o \ effects/Reverse.o \ + effects/ScienFilter.o \ effects/Silence.o \ effects/StereoToMono.o \ effects/TimeWarper.o \ diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp new file mode 100644 index 000000000..8a0442a69 --- /dev/null +++ b/src/effects/Biquad.cpp @@ -0,0 +1,51 @@ +#include "Biquad.h" + +#define square(a) ((a)*(a)) + +void Biquad_Process (BiquadStruct* pBQ, int iNumSamples) +{ + float* pfIn = pBQ->pfIn; + float* pfOut = pBQ->pfOut; + float fPrevIn = pBQ->fPrevIn; + float fPrevPrevIn = pBQ->fPrevPrevIn; + float fPrevOut = pBQ->fPrevOut; + float fPrevPrevOut = pBQ->fPrevPrevOut; + for (int i = 0; i < iNumSamples; i++) + { + float fIn = *pfIn++; + *pfOut = fIn * pBQ->fNumerCoeffs [0] + + fPrevIn * pBQ->fNumerCoeffs [1] + + fPrevPrevIn * pBQ->fNumerCoeffs [2] - + fPrevOut * pBQ->fDenomCoeffs [0] - + fPrevPrevOut * pBQ->fDenomCoeffs [1]; + fPrevPrevIn = fPrevIn; + fPrevIn = fIn; + fPrevPrevOut = fPrevOut; + fPrevOut = *pfOut++; + } + pBQ->fPrevIn = fPrevIn; + pBQ->fPrevPrevIn = fPrevPrevIn; + pBQ->fPrevOut = fPrevOut; + pBQ->fPrevPrevOut = fPrevPrevOut; +} + +void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI) +{ + float fDenom = square(fDenomR) + square(fDenomI); + *pfQuotientR = (fNumerR * fDenomR + fNumerI * fDenomI) / fDenom; + *pfQuotientI = (fNumerI * fDenomR - fNumerR * fDenomI) / fDenom; +} + +bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY) +{ + float fDenom = square (1 - fSX) + square (fSY); + *pfZX = (1 - square (fSX) - square (fSY)) / fDenom; + *pfZY = 2 * fSY / fDenom; + return true; +} + +float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2) +{ + return square (fX1 - fX2) + square (fY1 - fY2); +} + diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h new file mode 100644 index 000000000..45a27a3cd --- /dev/null +++ b/src/effects/Biquad.h @@ -0,0 +1,18 @@ +#ifndef __BIQUAD_H__ +#define __BIQUAD_H__ +typedef struct { + float* pfIn; + float* pfOut; + float fNumerCoeffs [3]; // B0 B1 B2 + float fDenomCoeffs [2]; // A1 A2 + float fPrevIn; + float fPrevPrevIn; + float fPrevOut; + float fPrevPrevOut; +} BiquadStruct; +void Biquad_Process (BiquadStruct* pBQ, int iNumSamples); +void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI); +bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY); +float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2); + +#endif diff --git a/src/effects/LoadEffects.cpp b/src/effects/LoadEffects.cpp index dc6269e3c..4d2d44886 100644 --- a/src/effects/LoadEffects.cpp +++ b/src/effects/LoadEffects.cpp @@ -38,6 +38,7 @@ #include "Reverb.h" #include "Reverse.h" #include "Silence.h" +#include "ScienFilter.h" #include "StereoToMono.h" #ifdef USE_SBSMS #include "TimeScale.h" @@ -263,6 +264,7 @@ void LoadEffects() em.RegisterEffect(new EffectRepeat()); em.RegisterEffect(new EffectReverb()); em.RegisterEffect(new EffectReverse()); + em.RegisterEffect(new EffectScienFilter()); em.RegisterEffect(new EffectStereoToMono(), HIDDEN_EFFECT);// NOT in normal effects list. em.RegisterEffect(new EffectTruncSilence(), SIMPLE_EFFECT); #ifdef USE_SBSMS diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp new file mode 100644 index 000000000..533da5f9e --- /dev/null +++ b/src/effects/ScienFilter.cpp @@ -0,0 +1,1248 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + Effect/ScienFilter.cpp + + 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); + } +} + + + +// 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 + diff --git a/src/effects/ScienFilter.h b/src/effects/ScienFilter.h new file mode 100644 index 000000000..5a64b960b --- /dev/null +++ b/src/effects/ScienFilter.h @@ -0,0 +1,341 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + EffectScienFilter.h + + Mitch Golden + Vaughan Johnson (Preview) + +***********************************************************************/ + +#ifndef __AUDACITY_EFFECT_SCIENFILTER__ +#define __AUDACITY_EFFECT_SCIENFILTER__ + +#define MAX_FILTER_ORDER 10 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if wxUSE_ACCESSIBILITY +#include +#endif + +#include "Effect.h" +#include "../WaveTrack.h" +#include "../widgets/Ruler.h" +#include "Biquad.h" + +class ScienFilterDialog; + + +class EffectScienFilter: public Effect { + +public: + + EffectScienFilter(); + virtual ~EffectScienFilter(); + + virtual wxString GetEffectName() { + return wxString(_("Scientific Filter...")); + } + + virtual std::set GetEffectCategories() { + std::set result; + result.insert(wxT("http://lv2plug.in/ns/lv2core#EQPlugin")); + return result; + } + + virtual wxString GetEffectIdentifier() { + return wxString(wxT("Scientific Filter")); + } + + virtual wxString GetEffectAction() { + return wxString(_("Performing ScienFilter")); + } + + virtual bool Init(); + virtual bool PromptUser(); + virtual bool DontPromptUser(); + virtual bool TransferParameters( Shuttle & shuttle ); + bool CalcFilterCoeffs (void); + + virtual bool Process(); + + // Lowest frequency to display in response graph + enum {loFreqI=20}; + +private: + bool ProcessOne(int count, WaveTrack * t, + sampleCount start, sampleCount len); + + void Filter(sampleCount len, + float *buffer); + void ReadPrefs(); + + //int mM; + + float mCutoff; + int mOrder; + float mRipple; + float mStopbandRipple; + int mFilterType; // Butterworth etc. + int mFilterSubtype; // lowpass, highpass + BiquadStruct* mpBiquad[5]; // MAX_ORDER/2 + + double mdBMax; + double mdBMin; + bool mPrompting; + bool mEditingBatchParams; + +public: + +friend class ScienFilterDialog; +friend class ScienFilterPanel; +}; + + +class ScienFilterPanel: public wxPanel +{ +public: + ScienFilterPanel( double loFreq, double hiFreq, + ScienFilterDialog *parent, + wxWindowID id, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize); + ~ScienFilterPanel(); + +#if 0 +Needed only if user can draw in the graph + void OnMouseEvent(wxMouseEvent & event); + void OnCaptureLost(wxMouseCaptureLostEvent & event); +#endif + void OnPaint(wxPaintEvent & event); + void OnSize (wxSizeEvent & event); + + // We don't need or want to accept focus. + bool AcceptsFocus() const { return false; } + + float dBMax; + float dBMin; + +private: + + wxBitmap *mBitmap; + wxRect mEnvRect; + ScienFilterDialog *mParent; + int mWidth; + int mHeight; + + double mLoFreq; + double mHiFreq; + + DECLARE_EVENT_TABLE() +}; + + +// WDR: class declarations + +//---------------------------------------------------------------------------- +// ScienFilterDialog +//---------------------------------------------------------------------------- + +class ScienFilterDialog: public wxDialog //, public XMLTagHandler +{ +public: + // constructors and destructors + ScienFilterDialog(EffectScienFilter * effect, + double loFreq, double hiFreq, + //long windowSize, wxString CurveName, bool disallowCustom, + wxWindow *parent, wxWindowID id, + const wxString &title, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxDEFAULT_DIALOG_STYLE ); + ~ScienFilterDialog(); + + // WDR: method declarations for ScienFilterDialog + virtual bool Validate(); + virtual bool TransferDataToWindow(); + virtual bool TransferGraphLimitsFromWindow(); + virtual bool CalcFilter(EffectScienFilter* effect); + float FilterMagnAtFreq (float Freq); + + wxChoice* mFilterTypeCtl; + wxChoice* mFilterSubTypeCtl; + wxChoice* mFilterOrderCtl; + + float Cutoff; + int Order; + float Ripple; + float StopbandRipple; + int FilterType; // Butterworth etc. + int FilterSubtype; // lowpass, highpass + + float dBMin; + float dBMax; + int interp; + RulerPanel *dBRuler; + RulerPanel *freqRuler; + +private: + void MakeScienFilterDialog(); + void Finish(bool ok); + +private: + // WDR: member variable declarations for ScienFilterDialog + + enum + { + ID_FILTERPANEL = 10000, + ID_DBMAX, + ID_DBMIN, + ID_FILTER_TYPE, + ID_FILTER_SUBTYPE, + ID_FILTER_ORDER, + ID_RIPPLE, + ID_CUTOFF, + ID_STOPBAND_RIPPLE + }; + +private: + // WDR: handler declarations for ScienFilterDialog + void OnPaint( wxPaintEvent &event ); + void OnSize( wxSizeEvent &event ); + void OnErase( wxEraseEvent &event ); + void OnSlider( wxCommandEvent &event ); + + void OnOrder( wxCommandEvent &event ); + void OnCutoff( wxCommandEvent &event ); + void OnRipple( wxCommandEvent &event ); + void OnStopbandRipple( wxCommandEvent &event ); + void OnFilterType( wxCommandEvent &event ); + void OnFilterSubtype( wxCommandEvent &event ); + + void OnSliderDBMAX( wxCommandEvent &event ); + void OnSliderDBMIN( wxCommandEvent &event ); + void OnPreview(wxCommandEvent &event); + void OnOk( wxCommandEvent &event ); + void OnCancel( wxCommandEvent &event ); + void EnableDisableRippleCtl (int FilterType); +private: + EffectScienFilter * m_pEffect; + + double mLoFreq; + double mNyquist; + + ScienFilterPanel *mPanel; + wxSlider *dBMinSlider; + wxSlider *dBMaxSlider; + wxBoxSizer *szrV; + wxFlexGridSizer *szr3; + wxBoxSizer *szr4; + wxBoxSizer *szr2; + wxFlexGridSizer *szr1; + wxSize size; + wxTextCtrl* mRippleCtl; + wxTextCtrl* mStopbandRippleCtl; + wxTextCtrl* mCutoffCtl; + +private: + DECLARE_EVENT_TABLE() + +}; + +#if wxUSE_ACCESSIBILITY + +class SliderAx: public wxWindowAccessible +{ +public: + SliderAx(wxWindow * window, wxString fmt); + + virtual ~ SliderAx(); + + // Retrieves the address of an IDispatch interface for the specified child. + // All objects must support this property. + virtual wxAccStatus GetChild( int childId, wxAccessible** child ); + + // Gets the number of children. + virtual wxAccStatus GetChildCount(int* childCount); + + // 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." + virtual wxAccStatus GetDefaultAction( int childId, wxString *actionName ); + + // Returns the description for this object or a child. + virtual wxAccStatus GetDescription( int childId, wxString *description ); + + // 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'. + virtual wxAccStatus GetFocus( int *childId, wxAccessible **child ); + + // Returns help text for this object or a child, similar to tooltip text. + virtual wxAccStatus GetHelpText( int childId, wxString *helpText ); + + // Returns the keyboard shortcut for this object or child. + // Return e.g. ALT+K + virtual wxAccStatus GetKeyboardShortcut( int childId, wxString *shortcut ); + + // Returns the rectangle for this object (id = 0) or a child element (id > 0). + // rect is in screen coordinates. + virtual wxAccStatus GetLocation( wxRect& rect, int elementId ); + + // Gets the name of the specified object. + virtual wxAccStatus GetName( int childId, wxString *name ); + + // Returns a role constant. + virtual wxAccStatus GetRole( int childId, wxAccRole *role ); + + // 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 + virtual wxAccStatus GetSelections( wxVariant *selections ); + + // Returns a state constant. + virtual wxAccStatus GetState(int childId, long* state); + + // Returns a localized string representing the value for the object + // or child. + virtual wxAccStatus GetValue(int childId, wxString* strValue); + +private: + wxWindow *mParent; + wxString mFmt; +}; + +#endif // wxUSE_ACCESSIBILITY + +#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: 309f263d-748c-4dc0-9e68-9e86732890bb + diff --git a/win/Projects/Audacity/Audacity.vcproj b/win/Projects/Audacity/Audacity.vcproj index 743f54265..f912c025e 100644 --- a/win/Projects/Audacity/Audacity.vcproj +++ b/win/Projects/Audacity/Audacity.vcproj @@ -1099,6 +1099,14 @@ RelativePath="..\..\..\src\effects\BassTreble.h" > + + + + @@ -1327,6 +1335,14 @@ RelativePath="..\..\..\src\effects\SBSMSEffect.h" > + + + +