1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-12-24 09:31:13 +01:00
Files
audacity/src/effects/BassTreble.cpp
Paul Licameli e653b4aaf8 Eliminate Experimental.h, configure compile options instead...
... This makes it impossible to forget to include the EXPERIMENTAL definitions
(such as when cutting and pasting code) and so get unintended quiet changes of
behavior.

The EXPERIMENTAL flags are now specified instead in new file Experimental.cmake
2021-04-27 12:40:07 -04:00

534 lines
13 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Audacity(R) is copyright (c) 1999-2016 Audacity Team.
License: GPL v2. See License.txt.
BassTreble.cpp
Steve Daulton
******************************************************************//**
\class EffectBassTreble
\brief A high shelf and low shelf filter.
*//*******************************************************************/
#include "../Audacity.h"
#include "BassTreble.h"
#include "LoadEffects.h"
#include <math.h>
#include <algorithm>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/intl.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/slider.h>
#include "../Prefs.h"
#include "../Shuttle.h"
#include "../ShuttleGui.h"
#include "../WaveTrack.h"
#include "../widgets/valnum.h"
enum
{
ID_Bass = 10000,
ID_Treble,
ID_Gain,
ID_Link
};
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
Param( Bass, double, wxT("Bass"), 0.0, -30.0, 30.0, 1 );
Param( Treble, double, wxT("Treble"), 0.0, -30.0, 30.0, 1 );
Param( Gain, double, wxT("Gain"), 0.0, -30.0, 30.0, 1 );
Param( Link, bool, wxT("Link Sliders"), false, false, true, 1 );
// Used to communicate the type of the filter.
enum kShelfType
{
kBass,
kTreble
};
const ComponentInterfaceSymbol EffectBassTreble::Symbol
{ XO("Bass and Treble") };
namespace{ BuiltinEffectsModule::Registration< EffectBassTreble > reg; }
BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler)
EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider)
EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider)
EVT_SLIDER(ID_Gain, EffectBassTreble::OnGainSlider)
EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText)
EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText)
EVT_TEXT(ID_Gain, EffectBassTreble::OnGainText)
EVT_CHECKBOX(ID_Link, EffectBassTreble::OnLinkCheckbox)
END_EVENT_TABLE()
EffectBassTreble::EffectBassTreble()
{
mBass = DEF_Bass;
mTreble = DEF_Treble;
mGain = DEF_Gain;
mLink = DEF_Link;
SetLinearEffectFlag(true);
}
EffectBassTreble::~EffectBassTreble()
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectBassTreble::GetSymbol()
{
return Symbol;
}
TranslatableString EffectBassTreble::GetDescription()
{
return XO("Simple tone control effect");
}
wxString EffectBassTreble::ManualPage()
{
return wxT("Bass_and_Treble");
}
// EffectDefinitionInterface implementation
EffectType EffectBassTreble::GetType()
{
return EffectTypeProcess;
}
bool EffectBassTreble::SupportsRealtime()
{
#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
return true;
#else
return false;
#endif
}
// EffectClientInterface implementation
unsigned EffectBassTreble::GetAudioInCount()
{
return 1;
}
unsigned EffectBassTreble::GetAudioOutCount()
{
return 1;
}
bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
InstanceInit(mMaster, mSampleRate);
return true;
}
size_t EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
{
return InstanceProcess(mMaster, inBlock, outBlock, blockLen);
}
bool EffectBassTreble::RealtimeInitialize()
{
SetBlockSize(512);
mSlaves.clear();
return true;
}
bool EffectBassTreble::RealtimeAddProcessor(unsigned WXUNUSED(numChannels), float sampleRate)
{
EffectBassTrebleState slave;
InstanceInit(slave, sampleRate);
mSlaves.push_back(slave);
return true;
}
bool EffectBassTreble::RealtimeFinalize()
{
mSlaves.clear();
return true;
}
size_t EffectBassTreble::RealtimeProcess(int group,
float **inbuf,
float **outbuf,
size_t numSamples)
{
return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples);
}
bool EffectBassTreble::DefineParams( ShuttleParams & S ){
S.SHUTTLE_PARAM( mBass, Bass );
S.SHUTTLE_PARAM( mTreble, Treble );
S.SHUTTLE_PARAM( mGain, Gain );
S.SHUTTLE_PARAM( mLink, Link );
return true;
}
bool EffectBassTreble::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_Bass, mBass);
parms.Write(KEY_Treble, mTreble);
parms.Write(KEY_Gain, mGain);
parms.Write(KEY_Link, mLink);
return true;
}
bool EffectBassTreble::SetAutomationParameters(CommandParameters & parms)
{
ReadAndVerifyDouble(Bass);
ReadAndVerifyDouble(Treble);
ReadAndVerifyDouble(Gain);
ReadAndVerifyBool(Link);
mBass = Bass;
mTreble = Treble;
mGain = Gain;
mLink = Link;
return true;
}
bool EffectBassTreble::CheckWhetherSkipEffect()
{
return (mBass == 0.0 && mTreble == 0.0 && mGain == 0.0);
}
// Effect implementation
void EffectBassTreble::PopulateOrExchange(ShuttleGui & S)
{
S.SetBorder(5);
S.AddSpace(0, 5);
S.StartStatic(XO("Tone controls"));
{
S.StartMultiColumn(3, wxEXPAND);
{
S.SetStretchyCol(2);
// Bass control
mBassT = S.Id(ID_Bass)
.Name(XO("Bass (dB):"))
.Validator<FloatingPointValidator<double>>(
1, &mBass, NumValidatorStyle::DEFAULT, MIN_Bass, MAX_Bass)
.AddTextBox(XXO("Ba&ss (dB):"), wxT(""), 10);
mBassS = S.Id(ID_Bass)
.Name(XO("Bass"))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, MAX_Bass * SCL_Bass, MIN_Bass * SCL_Bass);
// Treble control
mTrebleT = S.Id(ID_Treble)
.Validator<FloatingPointValidator<double>>(
1, &mTreble, NumValidatorStyle::DEFAULT, MIN_Treble, MAX_Treble)
.AddTextBox(XXO("&Treble (dB):"), wxT(""), 10);
mTrebleS = S.Id(ID_Treble)
.Name(XO("Treble"))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, MAX_Treble * SCL_Treble, MIN_Treble * SCL_Treble);
}
S.EndMultiColumn();
}
S.EndStatic();
S.StartStatic(XO("Output"));
{
S.StartMultiColumn(3, wxEXPAND);
{
S.SetStretchyCol(2);
// Gain control
mGainT = S.Id(ID_Gain)
.Validator<FloatingPointValidator<double>>(
1, &mGain, NumValidatorStyle::DEFAULT, MIN_Gain, MAX_Gain)
.AddTextBox(XXO("&Volume (dB):"), wxT(""), 10);
mGainS = S.Id(ID_Gain)
.Name(XO("Level"))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, MAX_Gain * SCL_Gain, MIN_Gain * SCL_Gain);
}
S.EndMultiColumn();
S.StartMultiColumn(2, wxCENTER);
{
// Link checkbox
mLinkCheckBox = S.Id(ID_Link).AddCheckBox(XXO("&Link Volume control to Tone controls"),
DEF_Link);
}
S.EndMultiColumn();
}
S.EndStatic();
}
bool EffectBassTreble::TransferDataToWindow()
{
if (!mUIParent->TransferDataToWindow())
{
return false;
}
mBassS->SetValue((int) (mBass * SCL_Bass));
mTrebleS->SetValue((int) mTreble *SCL_Treble);
mGainS->SetValue((int) mGain * SCL_Gain);
mLinkCheckBox->SetValue(mLink);
return true;
}
bool EffectBassTreble::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
return true;
}
// EffectBassTreble implementation
void EffectBassTreble::InstanceInit(EffectBassTrebleState & data, float sampleRate)
{
data.samplerate = sampleRate;
data.slope = 0.4f; // same slope for both filters
data.hzBass = 250.0f; // could be tunable in a more advanced version
data.hzTreble = 4000.0f; // could be tunable in a more advanced version
data.a0Bass = 1;
data.a1Bass = 0;
data.a2Bass = 0;
data.b0Bass = 0;
data.b1Bass = 0;
data.b2Bass = 0;
data.a0Treble = 1;
data.a1Treble = 0;
data.a2Treble = 0;
data.b0Treble = 0;
data.b1Treble = 0;
data.b2Treble = 0;
data.xn1Bass = 0;
data.xn2Bass = 0;
data.yn1Bass = 0;
data.yn2Bass = 0;
data.xn1Treble = 0;
data.xn2Treble = 0;
data.yn1Treble = 0;
data.yn2Treble = 0;
data.bass = -1;
data.treble = -1;
data.gain = DB_TO_LINEAR(mGain);
}
// EffectClientInterface implementation
size_t EffectBassTreble::InstanceProcess(EffectBassTrebleState & data,
float **inBlock,
float **outBlock,
size_t blockLen)
{
float *ibuf = inBlock[0];
float *obuf = outBlock[0];
// Set value to ensure correct rounding
double oldBass = DB_TO_LINEAR(mBass);
double oldTreble = DB_TO_LINEAR(mTreble);
data.gain = DB_TO_LINEAR(mGain);
// Compute coefficients of the low shelf biquand IIR filter
if (data.bass != oldBass)
Coefficients(data.hzBass, data.slope, mBass, data.samplerate, kBass,
data.a0Bass, data.a1Bass, data.a2Bass,
data.b0Bass, data.b1Bass, data.b2Bass);
// Compute coefficients of the high shelf biquand IIR filter
if (data.treble != oldTreble)
Coefficients(data.hzTreble, data.slope, mTreble, data.samplerate, kTreble,
data.a0Treble, data.a1Treble, data.a2Treble,
data.b0Treble, data.b1Treble, data.b2Treble);
for (decltype(blockLen) i = 0; i < blockLen; i++) {
obuf[i] = DoFilter(data, ibuf[i]) * data.gain;
}
return blockLen;
}
// Effect implementation
void EffectBassTreble::Coefficients(double hz, double slope, double gain, double samplerate, int type,
double& a0, double& a1, double& a2,
double& b0, double& b1, double& b2)
{
double w = 2 * M_PI * hz / samplerate;
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(EffectBassTrebleState & data, float in)
{
// Bass filter
float out = (data.b0Bass * in + data.b1Bass * data.xn1Bass + data.b2Bass * data.xn2Bass -
data.a1Bass * data.yn1Bass - data.a2Bass * data.yn2Bass) / data.a0Bass;
data.xn2Bass = data.xn1Bass;
data.xn1Bass = in;
data.yn2Bass = data.yn1Bass;
data.yn1Bass = out;
// Treble filter
in = out;
out = (data.b0Treble * in + data.b1Treble * data.xn1Treble + data.b2Treble * data.xn2Treble -
data.a1Treble * data.yn1Treble - data.a2Treble * data.yn2Treble) / data.a0Treble;
data.xn2Treble = data.xn1Treble;
data.xn1Treble = in;
data.yn2Treble = data.yn1Treble;
data.yn1Treble = out;
return out;
}
void EffectBassTreble::OnBassText(wxCommandEvent & WXUNUSED(evt))
{
double oldBass = mBass;
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
if (mLink) UpdateGain(oldBass, kBass);
mBassS->SetValue((int) (mBass * SCL_Bass));
}
void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt))
{
double oldTreble = mTreble;
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
if (mLink) UpdateGain(oldTreble, kTreble);
mTrebleS->SetValue((int) (mTreble * SCL_Treble));
}
void EffectBassTreble::OnGainText(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
mGainS->SetValue((int) (mGain * SCL_Gain));
}
void EffectBassTreble::OnBassSlider(wxCommandEvent & evt)
{
double oldBass = mBass;
mBass = (double) evt.GetInt() / SCL_Bass;
mBassT->GetValidator()->TransferToWindow();
if (mLink) UpdateGain(oldBass, kBass);
EnableApply(mUIParent->Validate());
}
void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt)
{
double oldTreble = mTreble;
mTreble = (double) evt.GetInt() / SCL_Treble;
mTrebleT->GetValidator()->TransferToWindow();
if (mLink) UpdateGain(oldTreble, kTreble);
EnableApply(mUIParent->Validate());
}
void EffectBassTreble::OnGainSlider(wxCommandEvent & evt)
{
mGain = (double) evt.GetInt() / SCL_Gain;
mGainT->GetValidator()->TransferToWindow();
EnableApply(mUIParent->Validate());
}
void EffectBassTreble::OnLinkCheckbox(wxCommandEvent& /*evt*/)
{
mLink = mLinkCheckBox->GetValue();
}
void EffectBassTreble::UpdateGain(double oldVal, int control)
{
double newVal;
oldVal = (oldVal > 0)? oldVal / 2.0 : oldVal / 4.0;
if (control == kBass)
newVal = (mBass > 0)? mBass / 2.0 : mBass / 4.0;
else
newVal = (mTreble > 0)? mTreble / 2.0 : mTreble / 4.0;
mGain -= newVal - oldVal;
mGain = std::min(MAX_Gain, std::max(MIN_Gain, mGain));
mGainS->SetValue(mGain);
mGainT->GetValidator()->TransferToWindow();
}