499 lines
13 KiB
C++
499 lines
13 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
Audacity(R) is copyright (c) 1999-2013 Audacity Team.
|
|
License: GPL v2. See License.txt.
|
|
|
|
BassTreble.cpp
|
|
Steve Daulton
|
|
|
|
******************************************************************//**
|
|
|
|
\class EffectBassTreble
|
|
\brief A high shelf and low shelf filter.
|
|
|
|
The first pass applies the equalization and calculates the
|
|
peak value. The second pass, if enabled, normalizes to the
|
|
level set by the level control.
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
#include "BassTreble.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <wx/button.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/panel.h>
|
|
#include <wx/sizer.h>
|
|
|
|
#include "../Prefs.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../widgets/valnum.h"
|
|
|
|
enum
|
|
{
|
|
ID_Bass = 10000,
|
|
ID_Treble,
|
|
ID_Level,
|
|
ID_Normalize,
|
|
};
|
|
|
|
// Define keys, defaults, minimums, and maximums for the effect parameters
|
|
//
|
|
// Name Type Key Def Min Max Scale
|
|
Param( Bass, double, XO("Bass"), 0.0, -30.0, 30.0, 1 );
|
|
Param( Treble, double, XO("Treble"), 0.0, -30.0, 30.0, 1 );
|
|
Param( Level, double, XO("Level"), -1.0, -30.0, 0.0, 1 );
|
|
Param( Normalize, bool, XO("Normalize"), true, false, true, 1 );
|
|
|
|
// Sliders are integer, so range is x 10
|
|
// to allow 1 decimal place resolution
|
|
static const int kSliderScale = 10;
|
|
|
|
// Used to communicate the type of the filter.
|
|
enum kShelfType
|
|
{
|
|
kBass,
|
|
kTreble
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler)
|
|
EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider)
|
|
EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider)
|
|
EVT_SLIDER(ID_Level, EffectBassTreble::OnLevelSlider)
|
|
EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText)
|
|
EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText)
|
|
EVT_TEXT(ID_Level, EffectBassTreble::OnLevelText)
|
|
EVT_CHECKBOX(ID_Normalize, EffectBassTreble::OnNormalize)
|
|
END_EVENT_TABLE()
|
|
|
|
EffectBassTreble::EffectBassTreble()
|
|
{
|
|
dB_bass = DEF_Bass;
|
|
dB_treble = DEF_Treble;
|
|
dB_level = DEF_Level;
|
|
mbNormalize = DEF_Normalize;
|
|
|
|
SetLinearEffectFlag(false);
|
|
}
|
|
|
|
EffectBassTreble::~EffectBassTreble()
|
|
{
|
|
}
|
|
|
|
// IdentInterface implementation
|
|
|
|
wxString EffectBassTreble::GetSymbol()
|
|
{
|
|
return BASSTREBLE_PLUGIN_SYMBOL;
|
|
}
|
|
|
|
wxString EffectBassTreble::GetDescription()
|
|
{
|
|
return XO("Increases or decreases the lower frequencies and higher frequencies of your audio independently");
|
|
}
|
|
|
|
// EffectIdentInterface implementation
|
|
|
|
EffectType EffectBassTreble::GetType()
|
|
{
|
|
return EffectTypeProcess;
|
|
}
|
|
|
|
// EffectClientInterface implementation
|
|
|
|
int EffectBassTreble::GetAudioInCount()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int EffectBassTreble::GetAudioOutCount()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
|
|
{
|
|
if (GetPass() == 1)
|
|
{
|
|
const float slope = 0.4f; // same slope for both filters
|
|
const double hzBass = 250.0f;
|
|
const double hzTreble = 4000.0f;
|
|
|
|
//(re)initialise filter parameters
|
|
xn1Bass=xn2Bass=yn1Bass=yn2Bass=0;
|
|
xn1Treble=xn2Treble=yn1Treble=yn2Treble=0;
|
|
|
|
// Compute coefficents of the low shelf biquand IIR filter
|
|
Coefficents(hzBass, slope, dB_bass, kBass,
|
|
a0Bass, a1Bass, a2Bass,
|
|
b0Bass, b1Bass, b2Bass);
|
|
|
|
// Compute coefficents of the high shelf biquand IIR filter
|
|
Coefficents(hzTreble, slope, dB_treble, kTreble,
|
|
a0Treble, a1Treble, a2Treble,
|
|
b0Treble, b1Treble, b2Treble);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
sampleCount EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen)
|
|
{
|
|
float *ibuf = inBlock[0];
|
|
float *obuf = outBlock[0];
|
|
|
|
if (GetPass() == 1)
|
|
{
|
|
for (sampleCount i = 0; i < blockLen; i++)
|
|
{
|
|
obuf[i] = DoFilter(ibuf[i]) / mPreGain;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float gain = (pow(10.0, dB_level / 20.0f)) / mMax;
|
|
for (sampleCount i = 0; i < blockLen; i++)
|
|
{
|
|
// Normalize to specified level
|
|
obuf[i] = ibuf[i] * (mPreGain * gain);
|
|
}
|
|
}
|
|
|
|
return blockLen;
|
|
}
|
|
|
|
bool EffectBassTreble::GetAutomationParameters(EffectAutomationParameters & parms)
|
|
{
|
|
parms.Write(KEY_Bass, dB_bass);
|
|
parms.Write(KEY_Treble, dB_treble);
|
|
parms.Write(KEY_Level, dB_level);
|
|
parms.Write(KEY_Normalize, mbNormalize);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EffectBassTreble::SetAutomationParameters(EffectAutomationParameters & parms)
|
|
{
|
|
ReadAndVerifyDouble(Bass);
|
|
ReadAndVerifyDouble(Treble);
|
|
ReadAndVerifyDouble(Level);
|
|
ReadAndVerifyBool(Normalize);
|
|
|
|
dB_bass = Bass;
|
|
dB_treble = Treble;
|
|
dB_level = Level;
|
|
mbNormalize = Normalize;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Effect implementation
|
|
|
|
bool EffectBassTreble::Startup()
|
|
{
|
|
wxString base = wxT("/Effects/BassTreble/");
|
|
|
|
// 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))
|
|
{
|
|
int readBool;
|
|
gPrefs->Read(base + wxT("Bass"), &dB_bass, 0.0);
|
|
gPrefs->Read(base + wxT("Treble"), &dB_treble, 0.0);
|
|
gPrefs->Read(base + wxT("Level"), &dB_level, -1.0);
|
|
gPrefs->Read(base + wxT("Normalize"), &readBool, 1 );
|
|
|
|
// Validate data
|
|
dB_level = (dB_level > 0) ? 0 : dB_level;
|
|
mbNormalize = (readBool != 0);
|
|
|
|
SaveUserPreset(GetCurrentSettingsGroup());
|
|
|
|
// Do not migrate again
|
|
gPrefs->Write(base + wxT("Migrated"), true);
|
|
gPrefs->Flush();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EffectBassTreble::InitPass1()
|
|
{
|
|
mMax = 0.0;
|
|
|
|
// Integer format tracks require headroom to avoid clipping
|
|
// when saved between passes (bug 619)
|
|
|
|
if (mbNormalize) // don't need to calculate this if only doing one pass.
|
|
{
|
|
// Up to (gain + 6dB) headroom required for treble boost (experimental).
|
|
mPreGain = (dB_treble > 0) ? (dB_treble + 6.0) : 0.0;
|
|
if (dB_bass >= 0)
|
|
{
|
|
mPreGain = (mPreGain > dB_bass) ? mPreGain : dB_bass;
|
|
}
|
|
else
|
|
{
|
|
// Up to 6 dB headroom reaquired for bass cut (experimental)
|
|
mPreGain = (mPreGain > 6.0) ? mPreGain : 6.0;
|
|
}
|
|
mPreGain = (exp(log(10.0) * mPreGain / 20)); // to linear
|
|
}
|
|
else
|
|
{
|
|
mPreGain = 1.0; // Unity gain
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EffectBassTreble::InitPass2()
|
|
{
|
|
return mbNormalize && mMax != 0;
|
|
}
|
|
|
|
void EffectBassTreble::PopulateOrExchange(ShuttleGui & S)
|
|
{
|
|
S.StartVerticalLay(0);
|
|
{
|
|
S.StartStatic(wxT(""));
|
|
{
|
|
S.StartMultiColumn(3, wxEXPAND);
|
|
S.SetStretchyCol(2);
|
|
{
|
|
#ifdef __WXGTK__
|
|
// BoxSizer is to make first mnemonic work, on Linux.
|
|
wxPanel* cPanel = new wxPanel(S.GetParent(), wxID_ANY);
|
|
wxBoxSizer* cSizer = new wxBoxSizer(wxVERTICAL);
|
|
cPanel->SetSizer(cSizer);
|
|
#endif
|
|
|
|
// Bass control
|
|
FloatingPointValidator<double> vldBass(1, &dB_bass);
|
|
vldBass.SetRange(MIN_Bass, MAX_Bass);
|
|
mBassT = S.Id(ID_Bass).AddTextBox(_("&Bass (dB):"), wxT(""), 10);
|
|
mBassT->SetName(_("Bass (dB):"));
|
|
mBassT->SetValidator(vldBass);
|
|
|
|
S.SetStyle(wxSL_HORIZONTAL);
|
|
mBassS = S.Id(ID_Bass).AddSlider(wxT(""), 0, MAX_Bass * kSliderScale, MIN_Bass * kSliderScale);
|
|
mBassS->SetName(_("Bass"));
|
|
mBassS->SetPageSize(30);
|
|
|
|
// Treble control
|
|
FloatingPointValidator<double> vldTreble(1, &dB_treble);
|
|
vldTreble.SetRange(MIN_Treble, MAX_Treble);
|
|
mTrebleT = S.Id(ID_Treble).AddTextBox(_("&Treble (dB):"), wxT(""), 10);
|
|
mTrebleT->SetValidator(vldTreble);
|
|
|
|
S.SetStyle(wxSL_HORIZONTAL);
|
|
mTrebleS = S.Id(ID_Treble).AddSlider(wxT(""), 0, MAX_Treble * kSliderScale, MIN_Treble * kSliderScale);
|
|
mTrebleS->SetName(_("Treble"));
|
|
mTrebleS->SetPageSize(30);
|
|
|
|
// Level control
|
|
FloatingPointValidator<double> vldLevel(1, &dB_level);
|
|
vldLevel.SetRange(MIN_Level, MAX_Level);
|
|
mLevelT = S.Id(ID_Level).AddTextBox(_("&Level (dB):"), wxT(""), 10);
|
|
mLevelT->SetValidator(vldLevel);
|
|
|
|
S.SetStyle(wxSL_HORIZONTAL);
|
|
mLevelS = S.Id(ID_Level).AddSlider(wxT(""), 0, MAX_Level * kSliderScale, MIN_Level * kSliderScale);
|
|
mLevelS->SetName(_("Level"));
|
|
mLevelS->SetPageSize(30);
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
S.EndStatic();
|
|
|
|
// Normalize checkbox
|
|
S.StartHorizontalLay(wxLEFT, true);
|
|
{
|
|
mNormalizeCheckBox = S.Id(ID_Normalize).AddCheckBox(_("&Enable level control"),
|
|
DEF_Normalize ? wxT("true") : wxT("false"));
|
|
mWarning = S.AddVariableText(wxT(""), false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
return;
|
|
}
|
|
|
|
bool EffectBassTreble::TransferDataToWindow()
|
|
{
|
|
if (!mUIParent->TransferDataToWindow())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mBassS->SetValue((int) dB_bass * kSliderScale + 0.5);
|
|
mTrebleS->SetValue((int) dB_treble * kSliderScale + 0.5);
|
|
mLevelS->SetValue((int) dB_level * kSliderScale + 0.5);
|
|
mNormalizeCheckBox->SetValue(mbNormalize);
|
|
|
|
UpdateUI();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EffectBassTreble::TransferDataFromWindow()
|
|
{
|
|
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mbNormalize = mNormalizeCheckBox->GetValue();
|
|
|
|
return true;
|
|
}
|
|
|
|
// EffectBassTreble implementation
|
|
|
|
void EffectBassTreble::Coefficents(double hz, float slope, double gain, int type,
|
|
float& a0, float& a1, float& a2,
|
|
float& b0, float& b1, float& b2)
|
|
{
|
|
double w = 2 * M_PI * hz / mSampleRate;
|
|
double a = exp(log(10.0) * gain / 40);
|
|
double b = sqrt((a * a + 1) / slope - (pow((a - 1), 2)));
|
|
|
|
if (type == kBass)
|
|
{
|
|
b0 = a * ((a + 1) - (a - 1) * cos(w) + b * sin(w));
|
|
b1 = 2 * a * ((a - 1) - (a + 1) * cos(w));
|
|
b2 = a * ((a + 1) - (a - 1) * cos(w) - b * sin(w));
|
|
a0 = ((a + 1) + (a - 1) * cos(w) + b * sin(w));
|
|
a1 = -2 * ((a - 1) + (a + 1) * cos(w));
|
|
a2 = (a + 1) + (a - 1) * cos(w) - b * sin(w);
|
|
}
|
|
else //assumed kTreble
|
|
{
|
|
b0 = a * ((a + 1) + (a - 1) * cos(w) + b * sin(w));
|
|
b1 = -2 * a * ((a - 1) + (a + 1) * cos(w));
|
|
b2 = a * ((a + 1) + (a - 1) * cos(w) - b * sin(w));
|
|
a0 = ((a + 1) - (a - 1) * cos(w) + b * sin(w));
|
|
a1 = 2 * ((a - 1) - (a + 1) * cos(w));
|
|
a2 = (a + 1) - (a - 1) * cos(w) - b * sin(w);
|
|
}
|
|
}
|
|
|
|
float EffectBassTreble::DoFilter(float in)
|
|
{
|
|
// Bass filter
|
|
float out = (b0Bass * in + b1Bass * xn1Bass + b2Bass * xn2Bass -
|
|
a1Bass * yn1Bass - a2Bass * yn2Bass) / a0Bass;
|
|
xn2Bass = xn1Bass;
|
|
xn1Bass = in;
|
|
yn2Bass = yn1Bass;
|
|
yn1Bass = out;
|
|
|
|
// Treble filter
|
|
in = out;
|
|
out = (b0Treble * in + b1Treble * xn1Treble + b2Treble * xn2Treble -
|
|
a1Treble * yn1Treble - a2Treble * yn2Treble) / a0Treble;
|
|
xn2Treble = xn1Treble;
|
|
xn1Treble = in;
|
|
yn2Treble = yn1Treble;
|
|
yn1Treble = out;
|
|
|
|
// Retain the maximum value for use in the normalization pass
|
|
if(mMax < fabs(out))
|
|
mMax = fabs(out);
|
|
return out;
|
|
}
|
|
|
|
void EffectBassTreble::UpdateUI()
|
|
{
|
|
double bass, treble, level;
|
|
mBassT->GetValue().ToDouble(&bass);
|
|
mTrebleT->GetValue().ToDouble(&treble);
|
|
mLevelT->GetValue().ToDouble(&level);
|
|
bool enable = mNormalizeCheckBox->GetValue();
|
|
|
|
// Disallow level control if disabled
|
|
mLevelT->Enable(enable);
|
|
mLevelS->Enable(enable);
|
|
|
|
if (bass == 0 && treble == 0 && !enable)
|
|
{
|
|
// Disallow Apply if nothing to do
|
|
EnableApply(false);
|
|
mWarning->SetLabel(_(" No change to apply."));
|
|
}
|
|
else
|
|
{
|
|
if (level > 0 && enable)
|
|
{
|
|
// Disallow Apply if level enabled and > 0
|
|
EnableApply(false);
|
|
mWarning->SetLabel(_(": Maximum 0 dB."));
|
|
}
|
|
else
|
|
{
|
|
// Apply enabled
|
|
EnableApply(true);
|
|
mWarning->SetLabel(wxT(""));
|
|
}
|
|
}
|
|
}
|
|
|
|
void EffectBassTreble::OnBassText(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mBassT->GetValidator()->TransferFromWindow();
|
|
mBassS->SetValue((int) floor(dB_bass * kSliderScale + 0.5));
|
|
UpdateUI();
|
|
}
|
|
|
|
void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mTrebleT->GetValidator()->TransferFromWindow();
|
|
mTrebleS->SetValue((int) floor(dB_treble * kSliderScale + 0.5));
|
|
UpdateUI();
|
|
}
|
|
|
|
void EffectBassTreble::OnLevelText(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mLevelT->GetValidator()->TransferFromWindow();
|
|
mLevelS->SetValue((int) floor(dB_level * kSliderScale + 0.5));
|
|
UpdateUI();
|
|
}
|
|
|
|
void EffectBassTreble::OnBassSlider(wxCommandEvent & evt)
|
|
{
|
|
dB_bass = (double) evt.GetInt() / kSliderScale;
|
|
mBassT->GetValidator()->TransferToWindow();
|
|
UpdateUI();
|
|
}
|
|
|
|
void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt)
|
|
{
|
|
dB_treble = (double) evt.GetInt() / kSliderScale;
|
|
mTrebleT->GetValidator()->TransferToWindow();
|
|
UpdateUI();
|
|
}
|
|
|
|
void EffectBassTreble::OnLevelSlider(wxCommandEvent & evt)
|
|
{
|
|
dB_level = (double) evt.GetInt() / kSliderScale;
|
|
mLevelT->GetValidator()->TransferToWindow();
|
|
UpdateUI();
|
|
}
|
|
|
|
void EffectBassTreble::OnNormalize(wxCommandEvent& WXUNUSED(evt))
|
|
{
|
|
UpdateUI();
|
|
}
|