1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-17 16:40:07 +02:00
audacity/src/effects/ScienFilter.cpp

1242 lines
40 KiB
C++

/**********************************************************************
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 "Equalization.h" // For SliderAx
#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 <wx/bitmap.h>
//#include <wx/button.h>
#include <wx/msgdlg.h>
#include <wx/brush.h>
#include <wx/dcmemory.h>
#include <wx/event.h>
#include <wx/image.h>
#include <wx/intl.h>
//#include <wx/choice.h>
#include <wx/stattext.h>
#include <wx/string.h>
#include <wx/textdlg.h>
#include <wx/stdpaths.h>
#include <wx/settings.h>
//#include <wx/checkbox.h>
#if wxUSE_TOOLTIPS
#include <wx/tooltip.h>
#endif
#include <wx/utils.h>
#include <math.h>
#include <wx/arrimpl.cpp>
#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.);
/*i18n-hint: The 'scientific filter' is an audio effect. It's a low-pass or high-pass
filter with specfic characteristics.*/
ScienFilterDialog dlog(this, ((double)loFreqI), hiFreq, NULL, -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.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; i<mEnvRect.width; i++)
{
x = mEnvRect.x + i;
freq = pow(10., loLog + i*step); //Hz
yF = mParent->FilterMagnAtFreq (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);
/*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/
mFilterTypeCtl->Append (_("Butterworth"));
/*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
mFilterTypeCtl->Append (_("Chebyshev Type I"));
/*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
mFilterTypeCtl->Append (_("Chebyshev Type II"));
szr3->Add (mFilterTypeCtl);
/*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/
szr3->Add( new wxStaticText(this, wxID_ANY, _(" Order:")), wxRIGHT );
mFilterOrderCtl = new wxChoice (this, ID_FILTER_ORDER);
mFilterOrderCtl->Append (wxT("1"));
mFilterOrderCtl->Append (wxT("2"));
mFilterOrderCtl->Append (wxT("3"));
mFilterOrderCtl->Append (wxT("4"));
mFilterOrderCtl->Append (wxT("5"));
mFilterOrderCtl->Append (wxT("6"));
mFilterOrderCtl->Append (wxT("7"));
mFilterOrderCtl->Append (wxT("8"));
mFilterOrderCtl->Append (wxT("9"));
mFilterOrderCtl->Append (wxT("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, wxT("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, wxT("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);
}
}