audacia/src/effects/Distortion.cpp

1333 lines
42 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Distortion.cpp
Steve Daulton
// TODO: Add a graph display of the waveshaper equation.
// TODO: Allow the user to draw the graph.
******************************************************************//**
\class EffectDistortion
\brief A WaveShaper distortion effect.
*//*******************************************************************/
#include "Distortion.h"
#include "LoadEffects.h"
#include <cmath>
#include <algorithm>
#define _USE_MATH_DEFINES
// Belt and braces
#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif
#ifndef M_PI_2
#define M_PI_2 1.57079632679489661923132169163975
#endif
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/intl.h>
#include <wx/valgen.h>
#include <wx/log.h>
#include <wx/slider.h>
#include <wx/stattext.h>
#include "../Prefs.h"
#include "../Shuttle.h"
#include "../ShuttleGui.h"
#include "../widgets/valnum.h"
enum kTableType
{
kHardClip,
kSoftClip,
kHalfSinCurve,
kExpCurve,
kLogCurve,
kCubic,
kEvenHarmonics,
kSinCurve,
kLeveller,
kRectifier,
kHardLimiter,
nTableTypes
};
static const EnumValueSymbol kTableTypeStrings[nTableTypes] =
{
{ XO("Hard Clipping") },
{ XO("Soft Clipping") },
{ XO("Soft Overdrive") },
{ XO("Medium Overdrive") },
{ XO("Hard Overdrive") },
{ XO("Cubic Curve (odd harmonics)") },
{ XO("Even Harmonics") },
{ XO("Expand and Compress") },
{ XO("Leveller") },
{ XO("Rectifier Distortion") },
{ XO("Hard Limiter 1413") }
};
// Define keys, defaults, minimums, and maximums for the effect parameters
// (Note: 'Repeats' is the total number of times the effect is applied.)
//
// Name Type Key Def Min Max Scale
Param( TableTypeIndx, int, wxT("Type"), 0, 0, nTableTypes-1, 1 );
Param( DCBlock, bool, wxT("DC Block"), false, false, true, 1 );
Param( Threshold_dB, double, wxT("Threshold dB"), -6.0, -100.0, 0.0, 1000.0f );
Param( NoiseFloor, double, wxT("Noise Floor"), -70.0, -80.0, -20.0, 1 );
Param( Param1, double, wxT("Parameter 1"), 50.0, 0.0, 100.0, 1 );
Param( Param2, double, wxT("Parameter 2"), 50.0, 0.0, 100.0, 1 );
Param( Repeats, int, wxT("Repeats"), 1, 0, 5, 1 );
// How many samples are processed before recomputing the lookup table again
#define skipsamples 1000
const double MIN_Threshold_Linear DB_TO_LINEAR(MIN_Threshold_dB);
static const struct
{
const TranslatableString name;
EffectDistortion::Params params;
}
FactoryPresets[] =
{
// Table DCBlock threshold floor Param1 Param2 Repeats
// Defaults: 0 false -6.0 -70.0(off) 50.0 50.0 1
//
// xgettext:no-c-format
{ XO("Hard clip -12dB, 80% make-up gain"), { 0, 0, -12.0, -70.0, 0.0, 80.0, 0 } },
// xgettext:no-c-format
{ XO("Soft clip -12dB, 80% make-up gain"), { 1, 0, -12.0, -70.0, 50.0, 80.0, 0 } },
{ XO("Fuzz Box"), { 1, 0, -30.0, -70.0, 80.0, 80.0, 0 } },
{ XO("Walkie-talkie"), { 1, 0, -50.0, -70.0, 60.0, 80.0, 0 } },
{ XO("Blues drive sustain"), { 2, 0, -6.0, -70.0, 30.0, 80.0, 0 } },
{ XO("Light Crunch Overdrive"), { 3, 0, -6.0, -70.0, 20.0, 80.0, 0 } },
{ XO("Heavy Overdrive"), { 4, 0, -6.0, -70.0, 90.0, 80.0, 0 } },
{ XO("3rd Harmonic (Perfect Fifth)"), { 5, 0, -6.0, -70.0, 100.0, 60.0, 0 } },
{ XO("Valve Overdrive"), { 6, 1, -6.0, -70.0, 30.0, 40.0, 0 } },
{ XO("2nd Harmonic (Octave)"), { 6, 1, -6.0, -70.0, 50.0, 0.0, 0 } },
{ XO("Gated Expansion Distortion"), { 7, 0, -6.0, -70.0, 30.0, 80.0, 0 } },
{ XO("Leveller, Light, -70dB noise floor"), { 8, 0, -6.0, -70.0, 0.0, 50.0, 1 } },
{ XO("Leveller, Moderate, -70dB noise floor"), { 8, 0, -6.0, -70.0, 0.0, 50.0, 2 } },
{ XO("Leveller, Heavy, -70dB noise floor"), { 8, 0, -6.0, -70.0, 0.0, 50.0, 3 } },
{ XO("Leveller, Heavier, -70dB noise floor"), { 8, 0, -6.0, -70.0, 0.0, 50.0, 4 } },
{ XO("Leveller, Heaviest, -70dB noise floor"), { 8, 0, -6.0, -70.0, 0.0, 50.0, 5 } },
{ XO("Half-wave Rectifier"), { 9, 0, -6.0, -70.0, 50.0, 50.0, 0 } },
{ XO("Full-wave Rectifier"), { 9, 0, -6.0, -70.0, 100.0, 50.0, 0 } },
{ XO("Full-wave Rectifier (DC blocked)"), { 9, 1, -6.0, -70.0, 100.0, 50.0, 0 } },
{ XO("Percussion Limiter"), {10, 0, -12.0, -70.0, 100.0, 30.0, 0 } },
};
TranslatableString defaultLabel(int index)
{
static const TranslatableString names[] = {
XO("Upper Threshold"),
XO("Noise Floor"),
XO("Parameter 1"),
XO("Parameter 2"),
XO("Number of repeats"),
};
return names[ index ];
}
//
// EffectDistortion
//
const ComponentInterfaceSymbol EffectDistortion::Symbol
{ XO("Distortion") };
namespace{ BuiltinEffectsModule::Registration< EffectDistortion > reg; }
BEGIN_EVENT_TABLE(EffectDistortion, wxEvtHandler)
EVT_CHOICE(ID_Type, EffectDistortion::OnTypeChoice)
EVT_CHECKBOX(ID_DCBlock, EffectDistortion::OnDCBlockCheckbox)
EVT_TEXT(ID_Threshold, EffectDistortion::OnThresholdText)
EVT_SLIDER(ID_Threshold, EffectDistortion::OnThresholdSlider)
EVT_TEXT(ID_NoiseFloor, EffectDistortion::OnNoiseFloorText)
EVT_SLIDER(ID_NoiseFloor, EffectDistortion::OnNoiseFloorSlider)
EVT_TEXT(ID_Param1, EffectDistortion::OnParam1Text)
EVT_SLIDER(ID_Param1, EffectDistortion::OnParam1Slider)
EVT_TEXT(ID_Param2, EffectDistortion::OnParam2Text)
EVT_SLIDER(ID_Param2, EffectDistortion::OnParam2Slider)
EVT_TEXT(ID_Repeats, EffectDistortion::OnRepeatsText)
EVT_SLIDER(ID_Repeats, EffectDistortion::OnRepeatsSlider)
END_EVENT_TABLE()
EffectDistortion::EffectDistortion()
{
wxASSERT(nTableTypes == WXSIZEOF(kTableTypeStrings));
mParams.mTableChoiceIndx = DEF_TableTypeIndx;
mParams.mDCBlock = DEF_DCBlock;
mParams.mThreshold_dB = DEF_Threshold_dB;
mThreshold = DB_TO_LINEAR(mParams.mThreshold_dB);
mParams.mNoiseFloor = DEF_NoiseFloor;
mParams.mParam1 = DEF_Param1;
mParams.mParam2 = DEF_Param2;
mParams.mRepeats = DEF_Repeats;
mMakeupGain = 1.0;
mbSavedFilterState = DEF_DCBlock;
SetLinearEffectFlag(false);
}
EffectDistortion::~EffectDistortion()
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectDistortion::GetSymbol()
{
return Symbol;
}
TranslatableString EffectDistortion::GetDescription()
{
return XO("Waveshaping distortion effect");
}
ManualPageID EffectDistortion::ManualPage()
{
return L"Distortion";
}
// EffectDefinitionInterface implementation
EffectType EffectDistortion::GetType()
{
return EffectTypeProcess;
}
bool EffectDistortion::SupportsRealtime()
{
#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
return true;
#else
return false;
#endif
}
// EffectClientInterface implementation
unsigned EffectDistortion::GetAudioInCount()
{
return 1;
}
unsigned EffectDistortion::GetAudioOutCount()
{
return 1;
}
bool EffectDistortion::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
InstanceInit(mMaster, mSampleRate);
return true;
}
size_t EffectDistortion::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
{
return InstanceProcess(mMaster, inBlock, outBlock, blockLen);
}
bool EffectDistortion::RealtimeInitialize()
{
SetBlockSize(512);
mSlaves.clear();
return true;
}
bool EffectDistortion::RealtimeAddProcessor(unsigned WXUNUSED(numChannels), float sampleRate)
{
EffectDistortionState slave;
InstanceInit(slave, sampleRate);
mSlaves.push_back(slave);
return true;
}
bool EffectDistortion::RealtimeFinalize()
{
mSlaves.clear();
return true;
}
size_t EffectDistortion::RealtimeProcess(int group,
float **inbuf,
float **outbuf,
size_t numSamples)
{
return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples);
}
bool EffectDistortion::DefineParams( ShuttleParams & S ){
S.SHUTTLE_ENUM_PARAM( mParams.mTableChoiceIndx, TableTypeIndx,
kTableTypeStrings, nTableTypes );
S.SHUTTLE_PARAM( mParams.mDCBlock, DCBlock );
S.SHUTTLE_PARAM( mParams.mThreshold_dB, Threshold_dB );
S.SHUTTLE_PARAM( mParams.mNoiseFloor, NoiseFloor );
S.SHUTTLE_PARAM( mParams.mParam1, Param1 );
S.SHUTTLE_PARAM( mParams.mParam2, Param2 );
S.SHUTTLE_PARAM( mParams.mRepeats, Repeats );
return true;
}
bool EffectDistortion::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_TableTypeIndx,
kTableTypeStrings[mParams.mTableChoiceIndx].Internal());
parms.Write(KEY_DCBlock, mParams.mDCBlock);
parms.Write(KEY_Threshold_dB, mParams.mThreshold_dB);
parms.Write(KEY_NoiseFloor, mParams.mNoiseFloor);
parms.Write(KEY_Param1, mParams.mParam1);
parms.Write(KEY_Param2, mParams.mParam2);
parms.Write(KEY_Repeats, mParams.mRepeats);
return true;
}
bool EffectDistortion::SetAutomationParameters(CommandParameters & parms)
{
ReadAndVerifyEnum(TableTypeIndx, kTableTypeStrings, nTableTypes);
ReadAndVerifyBool(DCBlock);
ReadAndVerifyDouble(Threshold_dB);
ReadAndVerifyDouble(NoiseFloor);
ReadAndVerifyDouble(Param1);
ReadAndVerifyDouble(Param2);
ReadAndVerifyInt(Repeats);
mParams.mTableChoiceIndx = TableTypeIndx;
mParams.mDCBlock = DCBlock;
mParams.mThreshold_dB = Threshold_dB;
mParams.mNoiseFloor = NoiseFloor;
mParams.mParam1 = Param1;
mParams.mParam2 = Param2;
mParams.mRepeats = Repeats;
return true;
}
RegistryPaths EffectDistortion::GetFactoryPresets()
{
RegistryPaths names;
for (size_t i = 0; i < WXSIZEOF(FactoryPresets); i++)
{
names.push_back( FactoryPresets[i].name.Translation() );
}
return names;
}
bool EffectDistortion::LoadFactoryPreset(int id)
{
if (id < 0 || id >= (int) WXSIZEOF(FactoryPresets))
{
return false;
}
mParams = FactoryPresets[id].params;
mThreshold = DB_TO_LINEAR(mParams.mThreshold_dB);
if (mUIDialog)
{
TransferDataToWindow();
}
return true;
}
// Effect implementation
void EffectDistortion::PopulateOrExchange(ShuttleGui & S)
{
S.AddSpace(0, 5);
S.StartVerticalLay();
{
S.StartMultiColumn(4, wxCENTER);
{
mTypeChoiceCtrl = S.Id(ID_Type)
.MinSize( { -1, -1 } )
.Validator<wxGenericValidator>(&mParams.mTableChoiceIndx)
.AddChoice(XXO("Distortion type:"),
Msgids(kTableTypeStrings, nTableTypes));
mDCBlockCheckBox = S.Id(ID_DCBlock).AddCheckBox(XXO("DC blocking filter"),
DEF_DCBlock);
}
S.EndMultiColumn();
S.AddSpace(0, 10);
S.StartStatic(XO("Threshold controls"));
{
S.StartMultiColumn(4, wxEXPAND);
S.SetStretchyCol(2);
{
// Allow space for first Column
S.AddSpace(250,0); S.AddSpace(0,0); S.AddSpace(0,0); S.AddSpace(0,0);
// Upper threshold control
mThresholdTxt = S.AddVariableText(defaultLabel(0),
false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
mThresholdT = S.Id(ID_Threshold)
.Name(defaultLabel(0))
.Validator<FloatingPointValidator<double>>(
2, &mParams.mThreshold_dB, NumValidatorStyle::DEFAULT,
MIN_Threshold_dB, MAX_Threshold_dB)
.AddTextBox( {}, wxT(""), 10);
mThresholdS = S.Id(ID_Threshold)
.Name(defaultLabel(0))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0,
DB_TO_LINEAR(MAX_Threshold_dB) * SCL_Threshold_dB,
DB_TO_LINEAR(MIN_Threshold_dB) * SCL_Threshold_dB);
S.AddSpace(20, 0);
// Noise floor control
mNoiseFloorTxt = S.AddVariableText(defaultLabel(1),
false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
mNoiseFloorT = S.Id(ID_NoiseFloor)
.Name(defaultLabel(1))
.Validator<FloatingPointValidator<double>>(
2, &mParams.mNoiseFloor, NumValidatorStyle::DEFAULT,
MIN_NoiseFloor, MAX_NoiseFloor
)
.AddTextBox( {}, wxT(""), 10);
mNoiseFloorS = S.Id(ID_NoiseFloor)
.Name(defaultLabel(1))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, MAX_NoiseFloor, MIN_NoiseFloor);
S.AddSpace(20, 0);
}
S.EndMultiColumn();
}
S.EndStatic();
S.StartStatic(XO("Parameter controls"));
{
S.StartMultiColumn(4, wxEXPAND);
S.SetStretchyCol(2);
{
// Allow space for first Column
S.AddSpace(250,0); S.AddSpace(0,0); S.AddSpace(0,0); S.AddSpace(0,0);
// Parameter1 control
mParam1Txt = S.AddVariableText(defaultLabel(2),
false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
mParam1T = S.Id(ID_Param1)
.Name(defaultLabel(2))
.Validator<FloatingPointValidator<double>>(
2, &mParams.mParam1, NumValidatorStyle::DEFAULT,
MIN_Param1, MAX_Param1
)
.AddTextBox( {}, wxT(""), 10);
mParam1S = S.Id(ID_Param1)
.Name(defaultLabel(2))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, MAX_Param1, MIN_Param1);
S.AddSpace(20, 0);
// Parameter2 control
mParam2Txt = S.AddVariableText(defaultLabel(3),
false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
mParam2T = S.Id(ID_Param2)
.Name(defaultLabel(3))
.Validator<FloatingPointValidator<double>>(
2, &mParams.mParam2, NumValidatorStyle::DEFAULT,
MIN_Param2, MAX_Param2
)
.AddTextBox( {}, wxT(""), 10);
mParam2S = S.Id(ID_Param2)
.Name(defaultLabel(3))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, MAX_Param2, MIN_Param2);
S.AddSpace(20, 0);
// Repeats control
mRepeatsTxt = S.AddVariableText(defaultLabel(4),
false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
mRepeatsT = S.Id(ID_Repeats)
.Name(defaultLabel(4))
.Validator<IntegerValidator<int>>(
&mParams.mRepeats, NumValidatorStyle::DEFAULT,
MIN_Repeats, MAX_Repeats
)
.AddTextBox( {}, wxT(""), 10);
mRepeatsS = S.Id(ID_Repeats)
.Name(defaultLabel(4))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, DEF_Repeats, MAX_Repeats, MIN_Repeats);
S.AddSpace(20, 0);
}
S.EndMultiColumn();
}
S.EndStatic();
}
S.EndVerticalLay();
return;
}
bool EffectDistortion::TransferDataToWindow()
{
if (!mUIParent->TransferDataToWindow())
{
return false;
}
mThresholdS->SetValue((int) (mThreshold * SCL_Threshold_dB + 0.5));
mDCBlockCheckBox->SetValue(mParams.mDCBlock);
mNoiseFloorS->SetValue((int) mParams.mNoiseFloor + 0.5);
mParam1S->SetValue((int) mParams.mParam1 + 0.5);
mParam2S->SetValue((int) mParams.mParam2 + 0.5);
mRepeatsS->SetValue(mParams.mRepeats);
mbSavedFilterState = mParams.mDCBlock;
UpdateUI();
return true;
}
bool EffectDistortion::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
mThreshold = DB_TO_LINEAR(mParams.mThreshold_dB);
return true;
}
void EffectDistortion::InstanceInit(EffectDistortionState & data, float sampleRate)
{
data.samplerate = sampleRate;
data.skipcount = 0;
data.tablechoiceindx = mParams.mTableChoiceIndx;
data.dcblock = mParams.mDCBlock;
data.threshold = mParams.mThreshold_dB;
data.noisefloor = mParams.mNoiseFloor;
data.param1 = mParams.mParam1;
data.param2 = mParams.mParam2;
data.repeats = mParams.mRepeats;
// DC block filter variables
data.queuetotal = 0.0;
//std::queue<float>().swap(data.queuesamples);
while (!data.queuesamples.empty())
data.queuesamples.pop();
MakeTable();
return;
}
size_t EffectDistortion::InstanceProcess(EffectDistortionState& data, float** inBlock, float** outBlock, size_t blockLen)
{
float *ibuf = inBlock[0];
float *obuf = outBlock[0];
bool update = (mParams.mTableChoiceIndx == data.tablechoiceindx &&
mParams.mNoiseFloor == data.noisefloor &&
mParams.mThreshold_dB == data.threshold &&
mParams.mParam1 == data.param1 &&
mParams.mParam2 == data.param2 &&
mParams.mRepeats == data.repeats)? false : true;
double p1 = mParams.mParam1 / 100.0;
double p2 = mParams.mParam2 / 100.0;
data.tablechoiceindx = mParams.mTableChoiceIndx;
data.threshold = mParams.mThreshold_dB;
data.noisefloor = mParams.mNoiseFloor;
data.param1 = mParams.mParam1;
data.repeats = mParams.mRepeats;
for (decltype(blockLen) i = 0; i < blockLen; i++) {
if (update && ((data.skipcount++) % skipsamples == 0)) {
MakeTable();
}
switch (mParams.mTableChoiceIndx)
{
case kHardClip:
// Param2 = make-up gain.
obuf[i] = WaveShaper(ibuf[i]) * ((1 - p2) + (mMakeupGain * p2));
break;
case kSoftClip:
// Param2 = make-up gain.
obuf[i] = WaveShaper(ibuf[i]) * ((1 - p2) + (mMakeupGain * p2));
break;
case kHalfSinCurve:
obuf[i] = WaveShaper(ibuf[i]) * p2;
break;
case kExpCurve:
obuf[i] = WaveShaper(ibuf[i]) * p2;
break;
case kLogCurve:
obuf[i] = WaveShaper(ibuf[i]) * p2;
break;
case kCubic:
obuf[i] = WaveShaper(ibuf[i]) * p2;
break;
case kEvenHarmonics:
obuf[i] = WaveShaper(ibuf[i]);
break;
case kSinCurve:
obuf[i] = WaveShaper(ibuf[i]) * p2;
break;
case kLeveller:
obuf[i] = WaveShaper(ibuf[i]);
break;
case kRectifier:
obuf[i] = WaveShaper(ibuf[i]);
break;
case kHardLimiter:
// Mix equivalent to LADSPA effect's "Wet / Residual" mix
obuf[i] = (WaveShaper(ibuf[i]) * (p1 - p2)) + (ibuf[i] * p2);
break;
default:
obuf[i] = WaveShaper(ibuf[i]);
}
if (mParams.mDCBlock) {
obuf[i] = DCFilter(data, obuf[i]);
}
}
return blockLen;
}
void EffectDistortion::OnTypeChoice(wxCommandEvent& /*evt*/)
{
mTypeChoiceCtrl->GetValidator()->TransferFromWindow();
UpdateUI();
}
void EffectDistortion::OnDCBlockCheckbox(wxCommandEvent& /*evt*/)
{
mParams.mDCBlock = mDCBlockCheckBox->GetValue();
mbSavedFilterState = mParams.mDCBlock;
}
void EffectDistortion::OnThresholdText(wxCommandEvent& /*evt*/)
{
mThresholdT->GetValidator()->TransferFromWindow();
mThreshold = DB_TO_LINEAR(mParams.mThreshold_dB);
mThresholdS->SetValue((int) (mThreshold * SCL_Threshold_dB + 0.5));
}
void EffectDistortion::OnThresholdSlider(wxCommandEvent& evt)
{
mThreshold = (double) evt.GetInt() / SCL_Threshold_dB;
mParams.mThreshold_dB = wxMax(LINEAR_TO_DB(mThreshold), MIN_Threshold_dB);
mThreshold = std::max(MIN_Threshold_Linear, mThreshold);
mThresholdT->GetValidator()->TransferToWindow();
}
void EffectDistortion::OnNoiseFloorText(wxCommandEvent& /*evt*/)
{
mNoiseFloorT->GetValidator()->TransferFromWindow();
mNoiseFloorS->SetValue((int) floor(mParams.mNoiseFloor + 0.5));
}
void EffectDistortion::OnNoiseFloorSlider(wxCommandEvent& evt)
{
mParams.mNoiseFloor = (double) evt.GetInt();
mNoiseFloorT->GetValidator()->TransferToWindow();
}
void EffectDistortion::OnParam1Text(wxCommandEvent& /*evt*/)
{
mParam1T->GetValidator()->TransferFromWindow();
mParam1S->SetValue((int) floor(mParams.mParam1 + 0.5));
}
void EffectDistortion::OnParam1Slider(wxCommandEvent& evt)
{
mParams.mParam1 = (double) evt.GetInt();
mParam1T->GetValidator()->TransferToWindow();
}
void EffectDistortion::OnParam2Text(wxCommandEvent& /*evt*/)
{
mParam2T->GetValidator()->TransferFromWindow();
mParam2S->SetValue((int) floor(mParams.mParam2 + 0.5));
}
void EffectDistortion::OnParam2Slider(wxCommandEvent& evt)
{
mParams.mParam2 = (double) evt.GetInt();
mParam2T->GetValidator()->TransferToWindow();
}
void EffectDistortion::OnRepeatsText(wxCommandEvent& /*evt*/)
{
mRepeatsT->GetValidator()->TransferFromWindow();
mRepeatsS->SetValue(mParams.mRepeats);
}
void EffectDistortion::OnRepeatsSlider(wxCommandEvent& evt)
{
mParams.mRepeats = evt.GetInt();
mRepeatsT->GetValidator()->TransferToWindow();
}
void EffectDistortion::UpdateUI()
{
// set control text and names to match distortion type
switch (mParams.mTableChoiceIndx)
{
case kHardClip:
UpdateControlText(mThresholdT, mOldThresholdTxt, true);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, true, XO("Clipping level"));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Drive"));
UpdateControl(ID_Param2, true, XO("Make-up Gain"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
case kSoftClip:
UpdateControlText(mThresholdT, mOldThresholdTxt, true);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, true, XO("Clipping threshold"));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Hardness"));
UpdateControl(ID_Param2, true, XO("Make-up Gain"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
case kHalfSinCurve:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, true, XO("Output level"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
case kExpCurve:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, true, XO("Output level"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
case kLogCurve:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, true, XO("Output level"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
case kCubic:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, true);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, true, XO("Output level"));
UpdateControl(ID_Repeats, true, XO("Repeat processing"));
UpdateControl(ID_DCBlock, false, {});
break;
case kEvenHarmonics:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, true, XO("Harmonic brightness"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, true, {});
break;
case kSinCurve:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, true, XO("Output level"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
case kLeveller:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, true);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, false);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, true);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, true, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Levelling fine adjustment"));
UpdateControl(ID_Param2, false, defaultLabel(3));
UpdateControl(ID_Repeats, true, XO("Degree of Levelling"));
UpdateControl(ID_DCBlock, false, {});
break;
case kRectifier:
UpdateControlText(mThresholdT, mOldThresholdTxt, false);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, false);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, false, defaultLabel(0));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Distortion amount"));
UpdateControl(ID_Param2, false, defaultLabel(3));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, true, {});
break;
case kHardLimiter:
UpdateControlText(mThresholdT, mOldThresholdTxt, true);
UpdateControlText(mNoiseFloorT, mOldmNoiseFloorTxt, false);
UpdateControlText(mParam1T, mOldParam1Txt, true);
UpdateControlText(mParam2T, mOldParam2Txt, true);
UpdateControlText(mRepeatsT, mOldRepeatsTxt, false);
UpdateControl(ID_Threshold, true, XO("dB Limit"));
UpdateControl(ID_NoiseFloor, false, defaultLabel(1));
UpdateControl(ID_Param1, true, XO("Wet level"));
UpdateControl(ID_Param2, true, XO("Residual level"));
UpdateControl(ID_Repeats, false, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
break;
default:
UpdateControl(ID_Threshold, true, defaultLabel(0));
UpdateControl(ID_NoiseFloor, true, defaultLabel(1));
UpdateControl(ID_Param1, true, defaultLabel(2));
UpdateControl(ID_Param2, true, defaultLabel(3));
UpdateControl(ID_Repeats, true, defaultLabel(4));
UpdateControl(ID_DCBlock, false, {});
}
}
void EffectDistortion::UpdateControl(
control id, bool enabled, TranslatableString name)
{
auto suffix = XO("(Not Used):");
switch (id)
{
case ID_Threshold: {
/* i18n-hint: Control range. */
if (enabled) suffix = XO("(-100 to 0 dB):");
name.Join( suffix, wxT(" ") );
// Logarithmic slider is set indirectly
mThreshold = DB_TO_LINEAR(mParams.mThreshold_dB);
mThresholdS->SetValue((int) (mThreshold * SCL_Threshold_dB + 0.5));
auto translated = name.Translation();
mThresholdTxt->SetLabel(translated);
mThresholdS->SetName(translated);
mThresholdT->SetName(translated);
mThresholdS->Enable(enabled);
mThresholdT->Enable(enabled);
break;
}
case ID_NoiseFloor: {
/* i18n-hint: Control range. */
if (enabled) suffix = XO("(-80 to -20 dB):");
name.Join( suffix, wxT(" ") );
auto translated = name.Translation();
mNoiseFloorTxt->SetLabel(translated);
mNoiseFloorS->SetName(translated);
mNoiseFloorT->SetName(translated);
mNoiseFloorS->Enable(enabled);
mNoiseFloorT->Enable(enabled);
break;
}
case ID_Param1: {
/* i18n-hint: Control range. */
if (enabled) suffix = XO("(0 to 100):");
name.Join( suffix, wxT(" ") );
auto translated = name.Translation();
mParam1Txt->SetLabel(translated);
mParam1S->SetName(translated);
mParam1T->SetName(translated);
mParam1S->Enable(enabled);
mParam1T->Enable(enabled);
break;
}
case ID_Param2: {
/* i18n-hint: Control range. */
if (enabled) suffix = XO("(0 to 100):");
name.Join( suffix, wxT(" ") );
auto translated = name.Translation();
mParam2Txt->SetLabel(translated);
mParam2S->SetName(translated);
mParam2T->SetName(translated);
mParam2S->Enable(enabled);
mParam2T->Enable(enabled);
break;
}
case ID_Repeats: {
/* i18n-hint: Control range. */
if (enabled) suffix = XO("(0 to 5):");
name.Join( suffix, wxT(" ") );
auto translated = name.Translation();
mRepeatsTxt->SetLabel(translated);
mRepeatsS->SetName(translated);
mRepeatsT->SetName(translated);
mRepeatsS->Enable(enabled);
mRepeatsT->Enable(enabled);
break;
}
case ID_DCBlock: {
if (enabled) {
mDCBlockCheckBox->SetValue(mbSavedFilterState);
mParams.mDCBlock = mbSavedFilterState;
}
else {
mDCBlockCheckBox->SetValue(false);
mParams.mDCBlock = false;
}
mDCBlockCheckBox->Enable(enabled);
break;
}
default:
break;
}
}
void EffectDistortion::UpdateControlText(wxTextCtrl* textCtrl, wxString& string, bool enabled)
{
if (enabled) {
if (textCtrl->GetValue().empty())
textCtrl->SetValue(string);
else
string = textCtrl->GetValue();
}
else {
if (!textCtrl->GetValue().empty())
string = textCtrl->GetValue();
textCtrl->SetValue(wxT(""));
}
}
void EffectDistortion::MakeTable()
{
switch (mParams.mTableChoiceIndx)
{
case kHardClip:
HardClip();
break;
case kSoftClip:
SoftClip();
break;
case kHalfSinCurve:
HalfSinTable();
break;
case kExpCurve:
ExponentialTable();
break;
case kLogCurve:
LogarithmicTable();
break;
case kCubic:
CubicTable();
break;
case kEvenHarmonics:
EvenHarmonicTable();
break;
case kSinCurve:
SineTable();
break;
case kLeveller:
Leveller();
break;
case kRectifier:
Rectifier();
break;
case kHardLimiter:
HardLimiter();
break;
}
}
//
// Preset tables for gain lookup
//
void EffectDistortion::HardClip()
{
double lowThresh = 1 - mThreshold;
double highThresh = 1 + mThreshold;
for (int n = 0; n < TABLESIZE; n++) {
if (n < (STEPS * lowThresh))
mTable[n] = - mThreshold;
else if (n > (STEPS * highThresh))
mTable[n] = mThreshold;
else
mTable[n] = n/(double)STEPS - 1;
mMakeupGain = 1.0 / mThreshold;
}
}
void EffectDistortion::SoftClip()
{
double threshold = 1 + mThreshold;
double amount = std::pow(2.0, 7.0 * mParams.mParam1 / 100.0); // range 1 to 128
double peak = LogCurve(mThreshold, 1.0, amount);
mMakeupGain = 1.0 / peak;
mTable[STEPS] = 0.0; // origin
// positive half of table
for (int n = STEPS; n < TABLESIZE; n++) {
if (n < (STEPS * threshold)) // origin to threshold
mTable[n] = n/(float)STEPS - 1;
else
mTable[n] = LogCurve(mThreshold, n/(double)STEPS - 1, amount);
}
CopyHalfTable();
}
float EffectDistortion::LogCurve(double threshold, float value, double ratio)
{
return threshold + ((std::exp(ratio * (threshold - value)) - 1) / -ratio);
}
void EffectDistortion::ExponentialTable()
{
double amount = std::min(0.999, DB_TO_LINEAR(-1 * mParams.mParam1)); // avoid divide by zero
for (int n = STEPS; n < TABLESIZE; n++) {
double linVal = n/(float)STEPS;
double scale = -1.0 / (1.0 - amount); // unity gain at 0dB
double curve = std::exp((linVal - 1) * std::log(amount));
mTable[n] = scale * (curve -1);
}
CopyHalfTable();
}
void EffectDistortion::LogarithmicTable()
{
double amount = mParams.mParam1;
double stepsize = 1.0 / STEPS;
double linVal = 0;
if (amount == 0){
for (int n = STEPS; n < TABLESIZE; n++) {
mTable[n] = linVal;
linVal += stepsize;
}
}
else {
for (int n = STEPS; n < TABLESIZE; n++) {
mTable[n] = std::log(1 + (amount * linVal)) / std::log(1 + amount);
linVal += stepsize;
}
}
CopyHalfTable();
}
void EffectDistortion::HalfSinTable()
{
int iter = std::floor(mParams.mParam1 / 20.0);
double fractionalpart = (mParams.mParam1 / 20.0) - iter;
double stepsize = 1.0 / STEPS;
double linVal = 0;
for (int n = STEPS; n < TABLESIZE; n++) {
mTable[n] = linVal;
for (int i = 0; i < iter; i++) {
mTable[n] = std::sin(mTable[n] * M_PI_2);
}
mTable[n] += ((std::sin(mTable[n] * M_PI_2) - mTable[n]) * fractionalpart);
linVal += stepsize;
}
CopyHalfTable();
}
void EffectDistortion::CubicTable()
{
double amount = mParams.mParam1 * std::sqrt(3.0) / 100.0;
double gain = 1.0;
if (amount != 0.0)
gain = 1.0 / Cubic(std::min(amount, 1.0));
double stepsize = amount / STEPS;
double x = -amount;
if (amount == 0) {
for (int i = 0; i < TABLESIZE; i++) {
mTable[i] = (i / (double)STEPS) - 1.0;
}
}
else {
for (int i = 0; i < TABLESIZE; i++) {
mTable[i] = gain * Cubic(x);
for (int j = 0; j < mParams.mRepeats; j++) {
mTable[i] = gain * Cubic(mTable[i] * amount);
}
x += stepsize;
}
}
}
double EffectDistortion::Cubic(double x)
{
if (mParams.mParam1 == 0.0)
return x;
return x - (std::pow(x, 3.0) / 3.0);
}
void EffectDistortion::EvenHarmonicTable()
{
double amount = mParams.mParam1 / -100.0;
// double C = std::sin(std::max(0.001, mParams.mParam2) / 100.0) * 10.0;
double C = std::max(0.001, mParams.mParam2) / 10.0;
double step = 1.0 / STEPS;
double xval = -1.0;
for (int i = 0; i < TABLESIZE; i++) {
mTable[i] = ((1 + amount) * xval) -
(xval * (amount / std::tanh(C)) * std::tanh(C * xval));
xval += step;
}
}
void EffectDistortion::SineTable()
{
int iter = std::floor(mParams.mParam1 / 20.0);
double fractionalpart = (mParams.mParam1 / 20.0) - iter;
double stepsize = 1.0 / STEPS;
double linVal = 0.0;
for (int n = STEPS; n < TABLESIZE; n++) {
mTable[n] = linVal;
for (int i = 0; i < iter; i++) {
mTable[n] = (1.0 + std::sin((mTable[n] * M_PI) - M_PI_2)) / 2.0;
}
mTable[n] += (((1.0 + std::sin((mTable[n] * M_PI) - M_PI_2)) / 2.0) - mTable[n]) * fractionalpart;
linVal += stepsize;
}
CopyHalfTable();
}
void EffectDistortion::Leveller()
{
double noiseFloor = DB_TO_LINEAR(mParams.mNoiseFloor);
int numPasses = mParams.mRepeats;
double fractionalPass = mParams.mParam1 / 100.0;
const int numPoints = 6;
const double gainFactors[numPoints] = { 0.80, 1.00, 1.20, 1.20, 1.00, 0.80 };
double gainLimits[numPoints] = { 0.0001, 0.0, 0.1, 0.3, 0.5, 1.0 };
double addOnValues[numPoints];
gainLimits[1] = noiseFloor;
/* In the original Leveller effect, behaviour was undefined for threshold > 20 dB.
* If we want to support > 20 dB we need to scale the points to keep them non-decreasing.
*
* if (noiseFloor > gainLimits[2]) {
* for (int i = 3; i < numPoints; i++) {
* gainLimits[i] = noiseFloor + ((1 - noiseFloor)*((gainLimits[i] - 0.1) / 0.9));
* }
* gainLimits[2] = noiseFloor;
* }
*/
// Calculate add-on values
addOnValues[0] = 0.0;
for (int i = 0; i < numPoints-1; i++) {
addOnValues[i+1] = addOnValues[i] + (gainLimits[i] * (gainFactors[i] - gainFactors[1 + i]));
}
// Positive half of table.
// The original effect increased the 'strength' of the effect by
// repeated passes over the audio data.
// Here we model that more efficiently by repeated passes over a linear table.
for (int n = STEPS; n < TABLESIZE; n++) {
mTable[n] = ((double) (n - STEPS) / (double) STEPS);
for (int j = 0; j < numPasses; j++) {
// Find the highest index for gain adjustment
int index = numPoints - 1;
for (int i = index; i >= 0 && mTable[n] < gainLimits[i]; i--) {
index = i;
}
// the whole number of 'repeats'
mTable[n] = (mTable[n] * gainFactors[index]) + addOnValues[index];
}
// Extrapolate for fine adjustment.
// tiny fractions are not worth the processing time
if (fractionalPass > 0.001) {
int index = numPoints - 1;
for (int i = index; i >= 0 && mTable[n] < gainLimits[i]; i--) {
index = i;
}
mTable[n] += fractionalPass * ((mTable[n] * (gainFactors[index] - 1)) + addOnValues[index]);
}
}
CopyHalfTable();
}
void EffectDistortion::Rectifier()
{
double amount = (mParams.mParam1 / 50.0) - 1;
double stepsize = 1.0 / STEPS;
int index = STEPS;
// positive half of waveform is passed unaltered.
for (int n = 0; n <= STEPS; n++) {
mTable[index] = n * stepsize;
index += 1;
}
// negative half of table
index = STEPS - 1;
for (int n = 1; n <= STEPS; n++) {
mTable[index] = n * stepsize * amount;
index--;
}
}
void EffectDistortion::HardLimiter()
{
// The LADSPA "hardLimiter 1413" is basically hard clipping,
// but with a 'kind of' wet/dry mix:
// out = ((wet-residual)*clipped) + (residual*in)
HardClip();
}
// Helper functions for lookup tables
void EffectDistortion::CopyHalfTable()
{
// Copy negative half of table from positive half
int count = TABLESIZE - 1;
for (int n = 0; n < STEPS; n++) {
mTable[n] = -mTable[count];
count--;
}
}
float EffectDistortion::WaveShaper(float sample)
{
float out;
int index;
double xOffset;
double amount = 1;
switch (mParams.mTableChoiceIndx)
{
// Do any pre-processing here
case kHardClip:
// Pre-gain
amount = mParams.mParam1 / 100.0;
sample *= 1+amount;
break;
default: break;
}
index = std::floor(sample * STEPS) + STEPS;
index = wxMax<int>(wxMin<int>(index, 2 * STEPS - 1), 0);
xOffset = ((1 + sample) * STEPS) - index;
xOffset = wxMin<double>(wxMax<double>(xOffset, 0.0), 1.0); // Clip at 0dB
// linear interpolation: y = y0 + (y1-y0)*(x-x0)
out = mTable[index] + (mTable[index + 1] - mTable[index]) * xOffset;
return out;
}
float EffectDistortion::DCFilter(EffectDistortionState& data, float sample)
{
// Rolling average gives less offset at the start than an IIR filter.
const unsigned int queueLength = std::floor(data.samplerate / 20.0);
data.queuetotal += sample;
data.queuesamples.push(sample);
if (data.queuesamples.size() > queueLength) {
data.queuetotal -= data.queuesamples.front();
data.queuesamples.pop();
}
return sample - (data.queuetotal / data.queuesamples.size());
}