audacia/src/effects/ScienFilter.cpp

974 lines
26 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Effect/ScienFilter.cpp
Norm C
Mitch Golden
Vaughan Johnson (Preview)
*******************************************************************//**
\file ScienFilter.cpp
\brief Implements EffectScienFilter, EffectScienFilterPanel.
*//****************************************************************//**
\class EffectScienFilter
\brief An Effect that applies 'classical' IIR filters.
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 EffectScienFilterPanel
\brief EffectScienFilterPanel is used with EffectScienFilter and controls
a graph for EffectScienFilter.
*//*******************************************************************/
#include "ScienFilter.h"
#include "LoadEffects.h"
#include <math.h>
#include <float.h>
#include <wx/setup.h> // for wxUSE_* macros
#include <wx/brush.h>
#include <wx/choice.h>
#include <wx/dcclient.h>
#include <wx/dcmemory.h>
#include <wx/intl.h>
#include <wx/settings.h>
#include <wx/slider.h>
#include <wx/stattext.h>
#include <wx/utils.h>
#include <wx/valgen.h>
#include "../AColor.h"
#include "../AllThemeResources.h"
#include "../PlatformCompatibility.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../Shuttle.h"
#include "../ShuttleGui.h"
#include "../Theme.h"
#include "../WaveTrack.h"
#include "../widgets/valnum.h"
#include "../widgets/AudacityMessageBox.h"
#include "../widgets/Ruler.h"
#include "../widgets/WindowAccessible.h"
#if !defined(M_PI)
#define PI = 3.1415926535897932384626433832795
#else
#define PI M_PI
#endif
#define square(a) ((a)*(a))
enum
{
ID_FilterPanel = 10000,
ID_dBMax,
ID_dBMin,
ID_Type,
ID_SubType,
ID_Order,
ID_Ripple,
ID_Cutoff,
ID_StopbandRipple
};
enum kTypes
{
kButterworth,
kChebyshevTypeI,
kChebyshevTypeII,
nTypes
};
static const EnumValueSymbol kTypeStrings[nTypes] =
{
/*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/
{ XO("Butterworth") },
/*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
{ XO("Chebyshev Type I") },
/*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
{ XO("Chebyshev Type II") }
};
enum kSubTypes
{
kLowPass = Biquad::kLowPass,
kHighPass = Biquad::kHighPass,
nSubTypes = Biquad::nSubTypes
};
static const EnumValueSymbol kSubTypeStrings[nSubTypes] =
{
// These are acceptable dual purpose internal/visible names
{ XO("Lowpass") },
{ XO("Highpass") }
};
static_assert(nSubTypes == WXSIZEOF(kSubTypeStrings), "size mismatch");
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
Param( Type, int, wxT("FilterType"), kButterworth, 0, nTypes - 1, 1 );
Param( Subtype, int, wxT("FilterSubtype"), kLowPass, 0, nSubTypes - 1, 1 );
Param( Order, int, wxT("Order"), 1, 1, 10, 1 );
Param( Cutoff, float, wxT("Cutoff"), 1000.0, 1.0, FLT_MAX, 1 );
Param( Passband, float, wxT("PassbandRipple"), 1.0, 0.0, 100.0, 1 );
Param( Stopband, float, wxT("StopbandRipple"), 30.0, 0.0, 100.0, 1 );
//----------------------------------------------------------------------------
// EffectScienFilter
//----------------------------------------------------------------------------
const ComponentInterfaceSymbol EffectScienFilter::Symbol
{ XO("Classic Filters") };
#ifdef EXPERIMENTAL_SCIENCE_FILTERS
// true argument means don't automatically enable this effect
namespace{ BuiltinEffectsModule::Registration< EffectScienFilter > reg( true ); }
#endif
BEGIN_EVENT_TABLE(EffectScienFilter, wxEvtHandler)
EVT_SIZE(EffectScienFilter::OnSize)
EVT_SLIDER(ID_dBMax, EffectScienFilter::OnSliderDBMAX)
EVT_SLIDER(ID_dBMin, EffectScienFilter::OnSliderDBMIN)
EVT_CHOICE(ID_Order, EffectScienFilter::OnOrder)
EVT_CHOICE(ID_Type, EffectScienFilter::OnFilterType)
EVT_CHOICE(ID_SubType, EffectScienFilter::OnFilterSubtype)
EVT_TEXT(ID_Cutoff, EffectScienFilter::OnCutoff)
EVT_TEXT(ID_Ripple, EffectScienFilter::OnRipple)
EVT_TEXT(ID_StopbandRipple, EffectScienFilter::OnStopbandRipple)
END_EVENT_TABLE()
EffectScienFilter::EffectScienFilter()
{
mOrder = DEF_Order;
mFilterType = DEF_Type;
mFilterSubtype = DEF_Subtype;
mCutoff = DEF_Cutoff;
mRipple = DEF_Passband;
mStopbandRipple = DEF_Stopband;
SetLinearEffectFlag(true);
mOrderIndex = mOrder - 1;
mdBMin = -30.0;
mdBMax = 30.0;
mLoFreq = 20; // Lowest frequency to display in response graph
mNyquist = 44100.0 / 2.0; // only used during initialization, updated when effect is used
}
EffectScienFilter::~EffectScienFilter()
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectScienFilter::GetSymbol()
{
return Symbol;
}
TranslatableString EffectScienFilter::GetDescription()
{
/* i18n-hint: "infinite impulse response" */
return XO("Performs IIR filtering that emulates analog filters");
}
ManualPageID EffectScienFilter::ManualPage()
{
return L"Classic_Filters";
}
// EffectDefinitionInterface implementation
EffectType EffectScienFilter::GetType()
{
return EffectTypeProcess;
}
// EffectClientInterface implementation
unsigned EffectScienFilter::GetAudioInCount()
{
return 1;
}
unsigned EffectScienFilter::GetAudioOutCount()
{
return 1;
}
bool EffectScienFilter::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++)
mpBiquad[iPair].Reset();
return true;
}
size_t EffectScienFilter::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
{
float *ibuf = inBlock[0];
for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++)
{
mpBiquad[iPair].Process(ibuf, outBlock[0], blockLen);
ibuf = outBlock[0];
}
return blockLen;
}
bool EffectScienFilter::DefineParams( ShuttleParams & S ){
S.SHUTTLE_ENUM_PARAM( mFilterType, Type, kTypeStrings, nTypes );
S.SHUTTLE_ENUM_PARAM( mFilterSubtype, Subtype, kSubTypeStrings, nSubTypes );
S.SHUTTLE_PARAM( mOrder, Order );
S.SHUTTLE_PARAM( mCutoff, Cutoff );
S.SHUTTLE_PARAM( mRipple, Passband );
S.SHUTTLE_PARAM( mStopbandRipple, Stopband );
return true;
}
bool EffectScienFilter::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_Type, kTypeStrings[mFilterType].Internal());
parms.Write(KEY_Subtype, kSubTypeStrings[mFilterSubtype].Internal());
parms.Write(KEY_Order, mOrder);
parms.WriteFloat(KEY_Cutoff, mCutoff);
parms.WriteFloat(KEY_Passband, mRipple);
parms.WriteFloat(KEY_Stopband, mStopbandRipple);
return true;
}
bool EffectScienFilter::SetAutomationParameters(CommandParameters & parms)
{
ReadAndVerifyEnum(Type, kTypeStrings, nTypes);
ReadAndVerifyEnum(Subtype, kSubTypeStrings, nSubTypes);
ReadAndVerifyInt(Order);
ReadAndVerifyFloat(Cutoff);
ReadAndVerifyFloat(Passband);
ReadAndVerifyFloat(Stopband);
mFilterType = Type;
mFilterSubtype = Subtype;
mOrder = Order;
mCutoff = Cutoff;
mRipple = Passband;
mStopbandRipple = Stopband;
mOrderIndex = mOrder - 1;
CalcFilter();
return true;
}
// Effect implementation
bool EffectScienFilter::Startup()
{
wxString base = wxT("/SciFilter/");
// Migrate settings from 2.1.0 or before
// Already migrated, so bail
if (gPrefs->Exists(base + wxT("Migrated")))
{
return true;
}
// Load the old "current" settings
if (gPrefs->Exists(base))
{
double dTemp;
gPrefs->Read(base + wxT("Order"), &mOrder, 1);
mOrder = wxMax (1, mOrder);
mOrder = wxMin (MAX_Order, mOrder);
gPrefs->Read(base + wxT("FilterType"), &mFilterType, 0);
mFilterType = wxMax (0, mFilterType);
mFilterType = wxMin (2, mFilterType);
gPrefs->Read(base + wxT("FilterSubtype"), &mFilterSubtype, 0);
mFilterSubtype = wxMax (0, mFilterSubtype);
mFilterSubtype = wxMin (1, mFilterSubtype);
gPrefs->Read(base + wxT("Cutoff"), &dTemp, 1000.0);
mCutoff = (float)dTemp;
mCutoff = wxMax (1, mCutoff);
mCutoff = wxMin (100000, mCutoff);
gPrefs->Read(base + wxT("Ripple"), &dTemp, 1.0);
mRipple = dTemp;
mRipple = wxMax (0, mRipple);
mRipple = wxMin (100, mRipple);
gPrefs->Read(base + wxT("StopbandRipple"), &dTemp, 30.0);
mStopbandRipple = dTemp;
mStopbandRipple = wxMax (0, mStopbandRipple);
mStopbandRipple = wxMin (100, mStopbandRipple);
SaveUserPreset(GetCurrentSettingsGroup());
// Do not migrate again
gPrefs->Write(base + wxT("Migrated"), true);
gPrefs->Flush();
}
return true;
}
bool EffectScienFilter::Init()
{
int selcount = 0;
double rate = 0.0;
auto trackRange = inputTracks()->Selected< const WaveTrack >();
{
auto t = *trackRange.begin();
mNyquist =
(t
? t->GetRate()
: mProjectRate)
/ 2.0;
}
for (auto t : trackRange)
{
if (selcount == 0)
{
rate = t->GetRate();
}
else
{
if (t->GetRate() != rate)
{
Effect::MessageBox(
XO(
"To apply a filter, all selected tracks must have the same sample rate.") );
return false;
}
}
selcount++;
}
return true;
}
void EffectScienFilter::PopulateOrExchange(ShuttleGui & S)
{
S.AddSpace(5);
S.SetSizerProportion(1);
S.StartMultiColumn(3, wxEXPAND);
{
S.SetStretchyCol(1);
S.SetStretchyRow(0);
// -------------------------------------------------------------------
// ROW 1: Freq response panel and sliders for vertical scale
// -------------------------------------------------------------------
S.StartVerticalLay();
{
mdBRuler = safenew RulerPanel(
S.GetParent(), wxID_ANY, wxVERTICAL,
wxSize{ 100, 100 }, // Ruler can't handle small sizes
RulerPanel::Range{ 30.0, -120.0 },
Ruler::LinearDBFormat,
XO("dB"),
RulerPanel::Options{}
.LabelEdges(true)
);
S.SetBorder(1);
S.AddSpace(1, 1);
S.Prop(1)
.Position(wxALIGN_RIGHT | wxTOP)
.AddWindow(mdBRuler);
S.AddSpace(1, 1);
}
S.EndVerticalLay();
mPanel = safenew EffectScienFilterPanel(
S.GetParent(), wxID_ANY,
this, mLoFreq, mNyquist
);
S.SetBorder(5);
S.Prop(1)
.Position(wxEXPAND | wxRIGHT)
.MinSize( { -1, -1 } )
.AddWindow(mPanel);
S.StartVerticalLay();
{
S.AddVariableText(XO("+ dB"), false, wxCENTER);
mdBMaxSlider = S.Id(ID_dBMax)
.Name(XO("Max dB"))
.Style(wxSL_VERTICAL | wxSL_INVERSE)
.AddSlider( {}, 10, 20, 0);
#if wxUSE_ACCESSIBILITY
mdBMaxSlider->SetAccessible(safenew SliderAx(mdBMaxSlider, XO("%d dB")));
#endif
mdBMinSlider = S.Id(ID_dBMin)
.Name(XO("Min dB"))
.Style(wxSL_VERTICAL | wxSL_INVERSE)
.AddSlider( {}, -10, -10, -120);
#if wxUSE_ACCESSIBILITY
mdBMinSlider->SetAccessible(safenew SliderAx(mdBMinSlider, XO("%d dB")));
#endif
S.AddVariableText(XO("- dB"), false, wxCENTER);
}
S.EndVerticalLay();
// -------------------------------------------------------------------
// ROW 2: Frequency ruler
// -------------------------------------------------------------------
S.AddSpace(1, 1);
mfreqRuler = safenew RulerPanel(
S.GetParent(), wxID_ANY, wxHORIZONTAL,
wxSize{ 100, 100 }, // Ruler can't handle small sizes
RulerPanel::Range{ mLoFreq, mNyquist },
Ruler::IntFormat,
{},
RulerPanel::Options{}
.Log(true)
.Flip(true)
.LabelEdges(true)
);
S.Prop(1)
.Position(wxEXPAND | wxALIGN_LEFT | wxRIGHT)
.AddWindow(mfreqRuler);
S.AddSpace(1, 1);
// -------------------------------------------------------------------
// ROW 3 and 4: Type, Order, Ripple, Subtype, Cutoff
// -------------------------------------------------------------------
S.AddSpace(1, 1);
S.SetSizerProportion(0);
S.StartMultiColumn(8, wxALIGN_CENTER);
{
wxASSERT(nTypes == WXSIZEOF(kTypeStrings));
mFilterTypeCtl = S.Id(ID_Type)
.Focus()
.Validator<wxGenericValidator>(&mFilterType)
.MinSize( { -1, -1 } )
.AddChoice(XXO("&Filter Type:"),
Msgids(kTypeStrings, nTypes)
);
mFilterOrderCtl = S.Id(ID_Order)
.Validator<wxGenericValidator>(&mOrderIndex)
.MinSize( { -1, -1 } )
/*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/
.AddChoice(XXO("O&rder:"),
[]{
TranslatableStrings orders;
for (int i = 1; i <= 10; i++)
orders.emplace_back( Verbatim("%d").Format( i ) );
return orders;
}()
);
S.AddSpace(1, 1);
mRippleCtlP = S.AddVariableText( XO("&Passband Ripple:"),
false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
mRippleCtl = S.Id(ID_Ripple)
.Name(XO("Passband Ripple (dB)"))
.Validator<FloatingPointValidator<float>>(
1, &mRipple, NumValidatorStyle::DEFAULT,
MIN_Passband, MAX_Passband)
.AddTextBox( {}, wxT(""), 10);
mRippleCtlU = S.AddVariableText(XO("dB"),
false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
mFilterSubTypeCtl = S.Id(ID_SubType)
.Validator<wxGenericValidator>(&mFilterSubtype)
.MinSize( { -1, -1 } )
.AddChoice(XXO("&Subtype:"),
Msgids(kSubTypeStrings, nSubTypes)
);
mCutoffCtl = S.Id(ID_Cutoff)
.Name(XO("Cutoff (Hz)"))
.Validator<FloatingPointValidator<float>>(
1, &mCutoff, NumValidatorStyle::DEFAULT,
MIN_Cutoff, mNyquist - 1)
.AddTextBox(XXO("C&utoff:"), wxT(""), 10);
S.AddUnits(XO("Hz"));
mStopbandRippleCtlP =
S.AddVariableText(XO("Minimum S&topband Attenuation:"),
false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
mStopbandRippleCtl = S.Id(ID_StopbandRipple)
.Name(XO("Minimum S&topband Attenuation (dB)"))
.Validator<FloatingPointValidator<float>>(
1, &mStopbandRipple, NumValidatorStyle::DEFAULT,
MIN_Stopband, MAX_Stopband)
.AddTextBox( {}, wxT(""), 10);
mStopbandRippleCtlU =
S.AddVariableText(XO("dB"),
false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
}
S.EndMultiColumn();
S.AddSpace(1, 1);
}
S.EndMultiColumn();
return;
}
//
// Populate the window with relevant variables
//
bool EffectScienFilter::TransferDataToWindow()
{
mOrderIndex = mOrder - 1;
if (!mUIParent->TransferDataToWindow())
{
return false;
}
mdBMinSlider->SetValue((int) mdBMin);
mdBMin = 0.0; // force refresh in TransferGraphLimitsFromWindow()
mdBMaxSlider->SetValue((int) mdBMax);
mdBMax = 0.0; // force refresh in TransferGraphLimitsFromWindow()
EnableDisableRippleCtl(mFilterType);
return TransferGraphLimitsFromWindow();
}
bool EffectScienFilter::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
mOrder = mOrderIndex + 1;
CalcFilter();
return true;
}
// EffectScienFilter implementation
//
// Retrieve data from the window
//
bool EffectScienFilter::TransferGraphLimitsFromWindow()
{
// Read the sliders and send to the panel
wxString tip;
bool rr = false;
int dB = mdBMinSlider->GetValue();
if (dB != mdBMin) {
rr = true;
mdBMin = dB;
tip.Printf(_("%d dB"), (int)mdBMin);
mdBMinSlider->SetToolTip(tip);
}
dB = mdBMaxSlider->GetValue();
if (dB != mdBMax) {
rr = true;
mdBMax = dB;
tip.Printf(_("%d dB"),(int)mdBMax);
mdBMaxSlider->SetToolTip(tip);
}
if (rr) {
mPanel->SetDbRange(mdBMin, mdBMax);
}
// Refresh ruler if values have changed
if (rr) {
int w1, w2, h;
mdBRuler->ruler.GetMaxSize(&w1, &h);
mdBRuler->ruler.SetRange(mdBMax, mdBMin);
mdBRuler->ruler.GetMaxSize(&w2, &h);
if( w1 != w2 ) // Reduces flicker
{
mdBRuler->SetSize(wxSize(w2,h));
mUIParent->Layout();
mfreqRuler->Refresh(false);
}
mdBRuler->Refresh(false);
}
mPanel->Refresh(false);
return true;
}
void EffectScienFilter::CalcFilter()
{
switch (mFilterType)
{
case kButterworth:
mpBiquad = Biquad::CalcButterworthFilter(mOrder, mNyquist, mCutoff, mFilterSubtype);
break;
case kChebyshevTypeI:
mpBiquad = Biquad::CalcChebyshevType1Filter(mOrder, mNyquist, mCutoff, mRipple, mFilterSubtype);
break;
case kChebyshevTypeII:
mpBiquad = Biquad::CalcChebyshevType2Filter(mOrder, mNyquist, mCutoff, mStopbandRipple, mFilterSubtype);
break;
}
}
float EffectScienFilter::FilterMagnAtFreq(float Freq)
{
float Magn;
if (Freq >= mNyquist)
Freq = mNyquist - 1; // prevent tan(PI/2)
float FreqWarped = tan (PI * Freq/(2*mNyquist));
if (mCutoff >= mNyquist)
mCutoff = mNyquist - 1;
float CutoffWarped = tan (PI * mCutoff/(2*mNyquist));
float fOverflowThresh = pow (10.0, 12.0 / (2*mOrder)); // once we exceed 10^12 there's not much to be gained and overflow could happen
switch (mFilterType)
{
case kButterworth: // Butterworth
default:
switch (mFilterSubtype)
{
case kLowPass: // lowpass
default:
if (FreqWarped/CutoffWarped > fOverflowThresh) // prevent pow() overflow
Magn = 0;
else
Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*mOrder)));
break;
case kHighPass: // highpass
if (FreqWarped/CutoffWarped > fOverflowThresh)
Magn = 1;
else
Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*mOrder) / (1 + pow (FreqWarped/CutoffWarped, 2*mOrder)));
break;
}
break;
case kChebyshevTypeI: // Chebyshev Type 1
double eps; eps = sqrt(pow (10.0, wxMax(0.001, mRipple)/10.0) - 1);
double chebyPolyVal;
switch (mFilterSubtype)
{
case 0: // lowpass
default:
chebyPolyVal = Biquad::ChebyPoly(mOrder, FreqWarped/CutoffWarped);
Magn = sqrt (1 / (1 + square(eps) * square(chebyPolyVal)));
break;
case 1:
chebyPolyVal = Biquad::ChebyPoly(mOrder, CutoffWarped/FreqWarped);
Magn = sqrt (1 / (1 + square(eps) * square(chebyPolyVal)));
break;
}
break;
case kChebyshevTypeII: // Chebyshev Type 2
eps = 1 / sqrt(pow (10.0, wxMax(0.001, mStopbandRipple)/10.0) - 1);
switch (mFilterSubtype)
{
case kLowPass: // lowpass
default:
chebyPolyVal = Biquad::ChebyPoly(mOrder, CutoffWarped/FreqWarped);
Magn = sqrt (1 / (1 + 1 / (square(eps) * square(chebyPolyVal))));
break;
case kHighPass:
chebyPolyVal = Biquad::ChebyPoly(mOrder, FreqWarped/CutoffWarped);
Magn = sqrt (1 / (1 + 1 / (square(eps) * square(chebyPolyVal))));
break;
}
break;
}
return Magn;
}
void EffectScienFilter::OnOrder(wxCommandEvent & WXUNUSED(evt))
{
mOrderIndex = mFilterOrderCtl->GetSelection();
mOrder = mOrderIndex + 1; // 0..n-1 -> 1..n
mPanel->Refresh(false);
}
void EffectScienFilter::OnFilterType(wxCommandEvent & WXUNUSED(evt))
{
mFilterType = mFilterTypeCtl->GetSelection();
EnableDisableRippleCtl(mFilterType);
mPanel->Refresh(false);
}
void EffectScienFilter::OnFilterSubtype(wxCommandEvent & WXUNUSED(evt))
{
mFilterSubtype = mFilterSubTypeCtl->GetSelection();
mPanel->Refresh(false);
}
void EffectScienFilter::OnCutoff(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
mPanel->Refresh(false);
}
void EffectScienFilter::OnRipple(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
mPanel->Refresh(false);
}
void EffectScienFilter::OnStopbandRipple(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
mPanel->Refresh(false);
}
void EffectScienFilter::OnSliderDBMIN(wxCommandEvent & WXUNUSED(evt))
{
TransferGraphLimitsFromWindow();
}
void EffectScienFilter::OnSliderDBMAX(wxCommandEvent & WXUNUSED(evt))
{
TransferGraphLimitsFromWindow();
}
void EffectScienFilter::OnSize(wxSizeEvent & evt)
{
// On Windows the Passband and Stopband boxes do not refresh properly
// on a resize...no idea why.
mUIParent->Refresh();
evt.Skip();
}
void EffectScienFilter::EnableDisableRippleCtl(int FilterType)
{
bool ripple;
bool stop;
if (FilterType == kButterworth) // Butterworth
{
ripple = false;
stop = false;
}
else if (FilterType == kChebyshevTypeI) // Chebyshev Type1
{
ripple = true;
stop = false;
}
else // Chebyshev Type2
{
ripple = false;
stop = true;
}
mRippleCtlP->Enable(ripple);
mRippleCtl->Enable(ripple);
mRippleCtlU->Enable(ripple);
mStopbandRippleCtlP->Enable(stop);
mStopbandRippleCtl->Enable(stop);
mStopbandRippleCtlU->Enable(stop);
}
//----------------------------------------------------------------------------
// EffectScienFilterPanel
//----------------------------------------------------------------------------
BEGIN_EVENT_TABLE(EffectScienFilterPanel, wxPanelWrapper)
EVT_PAINT(EffectScienFilterPanel::OnPaint)
EVT_SIZE(EffectScienFilterPanel::OnSize)
END_EVENT_TABLE()
EffectScienFilterPanel::EffectScienFilterPanel(
wxWindow *parent, wxWindowID winid,
EffectScienFilter *effect, double lo, double hi)
: wxPanelWrapper(parent, winid, wxDefaultPosition, wxSize(400, 200))
{
mEffect = effect;
mParent = parent;
mBitmap = NULL;
mWidth = 0;
mHeight = 0;
mLoFreq = 0.0;
mHiFreq = 0.0;
mDbMin = 0.0;
mDbMax = 0.0;
SetFreqRange(lo, hi);
}
EffectScienFilterPanel::~EffectScienFilterPanel()
{
}
void EffectScienFilterPanel::SetFreqRange(double lo, double hi)
{
mLoFreq = lo;
mHiFreq = hi;
Refresh(false);
}
void EffectScienFilterPanel::SetDbRange(double min, double max)
{
mDbMin = min;
mDbMax = max;
Refresh(false);
}
bool EffectScienFilterPanel::AcceptsFocus() const
{
return false;
}
bool EffectScienFilterPanel::AcceptsFocusFromKeyboard() const
{
return false;
}
void EffectScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
{
Refresh(false);
}
void EffectScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
{
wxPaintDC dc(this);
int width, height;
GetSize(&width, &height);
if (!mBitmap || mWidth != width || mHeight != height)
{
mWidth = width;
mHeight = height;
mBitmap = std::make_unique<wxBitmap>(mWidth, mHeight,24);
}
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, wxPENSTYLE_SOLID));
int center = (int) (mEnvRect.height * mDbMax / (mDbMax - mDbMin) + 0.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, wxPENSTYLE_SOLID));
double scale = (double) mEnvRect.height / (mDbMax - mDbMin); // pixels per dB
double yF; // gain at this freq
double loLog = log10(mLoFreq);
double step = log10(mHiFreq) - loLog;
step /= ((double) mEnvRect.width - 1.0);
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.0, loLog + i * step); //Hz
yF = mEffect->FilterMagnAtFreq (freq);
yF = LINEAR_TO_DB(yF);
if (yF < mDbMin)
{
yF = mDbMin;
}
yF = center-scale * yF;
if (yF > mEnvRect.height)
{
yF = (double) mEnvRect.height - 1.0;
}
if (yF < 0.0)
{
yF = 0.0;
}
y = (int) (yF + 0.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);
mEffect->mfreqRuler->ruler.DrawGrid(memDC, mEnvRect.height + 2, true, true, 0, 1);
mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width + 2, true, true, 1, 2);
dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE);
memDC.SelectObject(wxNullBitmap);
}