diff --git a/src/Makefile.am b/src/Makefile.am index 8333ecb31..b0dd2a48f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -327,6 +327,8 @@ audacity_SOURCES = \ effects/Compressor.h \ effects/Contrast.cpp \ effects/Contrast.h \ + effects/Distortion.cpp \ + effects/Distortion.h \ effects/DtmfGen.cpp \ effects/DtmfGen.h \ effects/Echo.cpp \ diff --git a/src/Makefile.in b/src/Makefile.in index 4b21d150e..7d2fdf4f8 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -351,6 +351,7 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \ effects/ChangeTempo.h effects/ClickRemoval.cpp \ effects/ClickRemoval.h effects/Compressor.cpp \ effects/Compressor.h effects/Contrast.cpp effects/Contrast.h \ + effects/Distortion.cpp effects/Distortion.h \ effects/DtmfGen.cpp effects/DtmfGen.h effects/Echo.cpp \ effects/Echo.h effects/Effect.cpp effects/Effect.h \ effects/EffectManager.cpp effects/EffectManager.h \ @@ -599,6 +600,7 @@ am_audacity_OBJECTS = $(am__objects_1) audacity-AboutDialog.$(OBJEXT) \ effects/audacity-ClickRemoval.$(OBJEXT) \ effects/audacity-Compressor.$(OBJEXT) \ effects/audacity-Contrast.$(OBJEXT) \ + effects/audacity-Distortion.$(OBJEXT) \ effects/audacity-DtmfGen.$(OBJEXT) \ effects/audacity-Echo.$(OBJEXT) \ effects/audacity-Effect.$(OBJEXT) \ @@ -1223,6 +1225,7 @@ audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \ effects/ChangeTempo.h effects/ClickRemoval.cpp \ effects/ClickRemoval.h effects/Compressor.cpp \ effects/Compressor.h effects/Contrast.cpp effects/Contrast.h \ + effects/Distortion.cpp effects/Distortion.h \ effects/DtmfGen.cpp effects/DtmfGen.h effects/Echo.cpp \ effects/Echo.h effects/Effect.cpp effects/Effect.h \ effects/EffectManager.cpp effects/EffectManager.h \ @@ -1603,6 +1606,8 @@ effects/audacity-Compressor.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-Contrast.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) +effects/audacity-Distortion.$(OBJEXT): effects/$(am__dirstamp) \ + effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-DtmfGen.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-Echo.$(OBJEXT): effects/$(am__dirstamp) \ @@ -2142,6 +2147,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-ClickRemoval.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Compressor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Contrast.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Distortion.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-DtmfGen.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Echo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Effect.Po@am__quote@ @@ -4146,6 +4152,20 @@ effects/audacity-Contrast.obj: effects/Contrast.cpp @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-Contrast.obj `if test -f 'effects/Contrast.cpp'; then $(CYGPATH_W) 'effects/Contrast.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/Contrast.cpp'; fi` +effects/audacity-Distortion.o: effects/Distortion.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-Distortion.o -MD -MP -MF effects/$(DEPDIR)/audacity-Distortion.Tpo -c -o effects/audacity-Distortion.o `test -f 'effects/Distortion.cpp' || echo '$(srcdir)/'`effects/Distortion.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-Distortion.Tpo effects/$(DEPDIR)/audacity-Distortion.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/Distortion.cpp' object='effects/audacity-Distortion.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-Distortion.o `test -f 'effects/Distortion.cpp' || echo '$(srcdir)/'`effects/Distortion.cpp + +effects/audacity-Distortion.obj: effects/Distortion.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-Distortion.obj -MD -MP -MF effects/$(DEPDIR)/audacity-Distortion.Tpo -c -o effects/audacity-Distortion.obj `if test -f 'effects/Distortion.cpp'; then $(CYGPATH_W) 'effects/Distortion.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/Distortion.cpp'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-Distortion.Tpo effects/$(DEPDIR)/audacity-Distortion.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/Distortion.cpp' object='effects/audacity-Distortion.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-Distortion.obj `if test -f 'effects/Distortion.cpp'; then $(CYGPATH_W) 'effects/Distortion.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/Distortion.cpp'; fi` + effects/audacity-DtmfGen.o: effects/DtmfGen.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-DtmfGen.o -MD -MP -MF effects/$(DEPDIR)/audacity-DtmfGen.Tpo -c -o effects/audacity-DtmfGen.o `test -f 'effects/DtmfGen.cpp' || echo '$(srcdir)/'`effects/DtmfGen.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-DtmfGen.Tpo effects/$(DEPDIR)/audacity-DtmfGen.Po diff --git a/src/effects/Distortion.cpp b/src/effects/Distortion.cpp new file mode 100644 index 000000000..6cb256c4b --- /dev/null +++ b/src/effects/Distortion.cpp @@ -0,0 +1,1263 @@ +/********************************************************************** + + 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 "../Audacity.h" +#include "Distortion.h" + +#include +#include +#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 +#include +#include +#include + +#include "../Prefs.h" +#include "../ShuttleGui.h" +#include "../widgets/valnum.h" + +enum kTableType +{ + kHardClip, + kSoftClip, + kHalfSinCurve, + kExpCurve, + kLogCurve, + kCubic, + kEvenHarmonics, + kSinCurve, + kLeveller, + kRectifier, + kHardLimiter, + kNumTableTypes +}; + +static const wxString kTableTypeStrings[kNumTableTypes] = +{ + XO("Hard Clip"), + 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, XO("Type"), 0, 0, kNumTableTypes-1, 1 ); +Param( DCBlock, bool, XO("DC Block"), false, false, true, 1 ); +Param( Threshold_dB, double, XO("Threshold dB"), -6.0, -100.0, 0.0, 1000.0f ); +Param( NoiseFloor, double, XO("Noise Floor"), -70.0, -80.0, -20.0, 1 ); +Param( Param1, double, XO("Parameter 1"), 50.0, 0.0, 100.0, 1 ); +Param( Param2, double, XO("Parameter 2"), 50.0, 0.0, 100.0, 1 ); +Param( Repeats, int, XO("Repeats"), 1, 0, 5, 1 ); + +// How many samples are processed before recomputing the lookup table again +#define skipsamples 100 + +const double MIN_Threshold_Linear DB_TO_LINEAR(MIN_Threshold_dB); + +static const struct +{ + const wxChar *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 + // + XO("Hard clip -12dB, 80% make-up gain"), { 0, 0, -12.0, -70.0, 0.0, 80.0, 1 }, + XO("Soft clip -12dB, 80% make-up gain"), { 1, 0, -12.0, -70.0, 50.0, 80.0, 1 }, + XO("Fuzz Box"), { 1, 0, -30.0, -70.0, 80.0, 80.0, 1 }, + XO("Blues drive sustain"), { 2, 0, -6.0, -60.0, 0.0, 80.0, 1 }, + XO("Light Crunch Overdrive"), { 3, 0, -6.0, -70.0, 20.0, 80.0, 1 }, + XO("Heavy Overdrive"), { 4, 0, -6.0, -70.0, 90.0, 80.0, 1 }, + XO("Gated Expansion Distortion"), { 7, 0, -6.0, -70.0, 30.0, 80.0, 1 }, + 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, 1 }, + XO("Full-wave Rectifier"), { 9, 0, -6.0, -70.0, 100.0, 50.0, 1 }, + XO("Full-wave Rectifier (DC blocked)"), { 9, 1, -6.0, -70.0, 100.0, 50.0, 1 }, +}; + +const wxString defaultLabel[5] = +{ + _("Upper Threshold"), + _("Noise Floor"), + _("Parameter 1"), + _("Parameter 2"), + _("Number of repeats"), +}; + +#include +WX_DEFINE_OBJARRAY(EffectDistortionStateArray); + +// +// EffectDistortion +// + +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(kNumTableTypes == 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; + + for (int i = 0; i < kNumTableTypes; i++) + { + mTableTypes.Add(wxGetTranslation(kTableTypeStrings[i])); + } + + SetLinearEffectFlag(false); +} + +EffectDistortion::~EffectDistortion() +{ +} + +// IdentInterface implementation + +wxString EffectDistortion::GetSymbol() +{ + return DISTORTION_PLUGIN_SYMBOL; +} + +wxString EffectDistortion::GetDescription() +{ + return XO("Waveshaping distortion effect"); +} + +// EffectIdentInterface implementation + +EffectType EffectDistortion::GetType() +{ + return EffectTypeProcess; +} + +bool EffectDistortion::SupportsRealtime() +{ +#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS) + return true; +#else + return false; +#endif +} + +// EffectClientInterface implementation + +int EffectDistortion::GetAudioInCount() +{ + return 1; +} + +int EffectDistortion::GetAudioOutCount() +{ + return 1; +} + +bool EffectDistortion::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) +{ + InstanceInit(mMaster, mSampleRate); + return true; +} + +sampleCount EffectDistortion::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) +{ + return InstanceProcess(mMaster, inBlock, outBlock, blockLen); +} + +bool EffectDistortion::RealtimeInitialize() +{ + SetBlockSize(512); + + mSlaves.Clear(); + + return true; +} + +bool EffectDistortion::RealtimeAddProcessor(int WXUNUSED(numChannels), float sampleRate) +{ + EffectDistortionState slave; + + InstanceInit(slave, sampleRate); + + mSlaves.Add(slave); + + return true; +} + +bool EffectDistortion::RealtimeFinalize() +{ + mSlaves.Clear(); + + return true; +} + +sampleCount EffectDistortion::RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples) +{ + + return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples); +} + +bool EffectDistortion::GetAutomationParameters(EffectAutomationParameters & parms) +{ + parms.Write(KEY_TableTypeIndx, kTableTypeStrings[mParams.mTableChoiceIndx]); + 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(EffectAutomationParameters & parms) +{ + ReadAndVerifyEnum(TableTypeIndx, wxArrayString(kNumTableTypes, kTableTypeStrings)); + 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; +} + +wxArrayString EffectDistortion::GetFactoryPresets() +{ + wxArrayString names; + + for (size_t i = 0; i < WXSIZEOF(FactoryPresets); i++) + { + names.Add(wxGetTranslation(FactoryPresets[i].name)); + } + + 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).AddChoice(_("Distortion type:"), wxT(""), &mTableTypes); + mTypeChoiceCtrl->SetValidator(wxGenericValidator(&mParams.mTableChoiceIndx)); + S.SetSizeHints(-1, -1); + + mDCBlockCheckBox = S.Id(ID_DCBlock).AddCheckBox(_("DC blocking filter"), + DEF_DCBlock ? wxT("true") : wxT("false")); + } + S.EndMultiColumn(); + S.AddSpace(0, 10); + + + S.StartStatic(_("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); + FloatingPointValidator vldThreshold(2, &mParams.mThreshold_dB); + vldThreshold.SetRange(MIN_Threshold_dB, MAX_Threshold_dB); + mThresholdT = S.Id(ID_Threshold).AddTextBox(wxT(""), wxT(""), 10); + mThresholdT->SetName(defaultLabel[0]); + mThresholdT->SetValidator(vldThreshold); + + S.SetStyle(wxSL_HORIZONTAL); + double maxLin = DB_TO_LINEAR(MAX_Threshold_dB) * SCL_Threshold_dB; + double minLin = DB_TO_LINEAR(MIN_Threshold_dB) * SCL_Threshold_dB; + mThresholdS = S.Id(ID_Threshold).AddSlider(wxT(""), 0, maxLin, minLin); + mThresholdS->SetName(defaultLabel[0]); + S.AddSpace(20, 0); + + // Noise floor control + mNoiseFloorTxt = S.AddVariableText(defaultLabel[1], false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + FloatingPointValidator vldfloor(2, &mParams.mNoiseFloor); + vldfloor.SetRange(MIN_NoiseFloor, MAX_NoiseFloor); + mNoiseFloorT = S.Id(ID_NoiseFloor).AddTextBox(wxT(""), wxT(""), 10); + mNoiseFloorT->SetName(defaultLabel[1]); + mNoiseFloorT->SetValidator(vldfloor); + + S.SetStyle(wxSL_HORIZONTAL); + mNoiseFloorS = S.Id(ID_NoiseFloor).AddSlider(wxT(""), 0, MAX_NoiseFloor, MIN_NoiseFloor); + mNoiseFloorS->SetName(defaultLabel[1]); + S.AddSpace(20, 0); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + S.StartStatic(_("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); + FloatingPointValidator vldparam1(2, &mParams.mParam1); + vldparam1.SetRange(MIN_Param1, MAX_Param1); + mParam1T = S.Id(ID_Param1).AddTextBox(wxT(""), wxT(""), 10); + mParam1T->SetName(defaultLabel[2]); + mParam1T->SetValidator(vldparam1); + + S.SetStyle(wxSL_HORIZONTAL); + mParam1S = S.Id(ID_Param1).AddSlider(wxT(""), 0, MAX_Param1, MIN_Param1); + mParam1S->SetName(defaultLabel[2]); + S.AddSpace(20, 0); + + // Parameter2 control + mParam2Txt = S.AddVariableText(defaultLabel[3], false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + FloatingPointValidator vldParam2(2, &mParams.mParam2); + vldParam2.SetRange(MIN_Param2, MAX_Param2); + mParam2T = S.Id(ID_Param2).AddTextBox(wxT(""), wxT(""), 10); + mParam2T->SetName(defaultLabel[3]); + mParam2T->SetValidator(vldParam2); + + S.SetStyle(wxSL_HORIZONTAL); + mParam2S = S.Id(ID_Param2).AddSlider(wxT(""), 0, MAX_Param2, MIN_Param2); + mParam2S->SetName(defaultLabel[3]); + S.AddSpace(20, 0); + + // Repeats control + mRepeatsTxt = S.AddVariableText(defaultLabel[4], false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + IntegerValidatorvldRepeats(&mParams.mRepeats); + vldRepeats.SetRange(MIN_Repeats, MAX_Repeats); + mRepeatsT = S.Id(ID_Repeats).AddTextBox(wxT(""), wxT(""), 10); + mRepeatsT->SetName(defaultLabel[4]); + mRepeatsT->SetValidator(vldRepeats); + + S.SetStyle(wxSL_HORIZONTAL); + mRepeatsS = S.Id(ID_Repeats).AddSlider(wxT(""), DEF_Repeats, MAX_Repeats, MIN_Repeats); + mRepeatsS->SetName(defaultLabel[4]); + 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); + + 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().swap(data.queuesamples); + + MakeTable(); + + return; +} + +sampleCount EffectDistortion::InstanceProcess(EffectDistortionState& data, float** inBlock, float** outBlock, sampleCount 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 (sampleCount 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, _("Clipping level")); + UpdateControl(ID_NoiseFloor, false, defaultLabel[1]); + UpdateControl(ID_Param1, true, _("Drive")); + UpdateControl(ID_Param2, true, _("Make-up Gain")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Clipping threshold")); + UpdateControl(ID_NoiseFloor, false, defaultLabel[1]); + UpdateControl(ID_Param1, true, _("Hardness")); + UpdateControl(ID_Param2, true, _("Make-up Gain")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, true, _("Output level")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, true, _("Output level")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, true, _("Output level")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, true, _("Output level")); + UpdateControl(ID_Repeats, true, _("Repeat processing")); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, true, _("Harmonic brightness")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, true, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, true, _("Output level")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Levelling fine adjustment")); + UpdateControl(ID_Param2, false, defaultLabel[3]); + UpdateControl(ID_Repeats, true, _("Degree of Levelling")); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, _("Distortion amount")); + UpdateControl(ID_Param2, false, defaultLabel[3]); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, true, wxEmptyString); + 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, _("dB Limit")); + UpdateControl(ID_NoiseFloor, false, defaultLabel[1]); + UpdateControl(ID_Param1, true, _("Wet level")); + UpdateControl(ID_Param2, true, _("Residual level")); + UpdateControl(ID_Repeats, false, defaultLabel[4]); + UpdateControl(ID_DCBlock, false, wxEmptyString); + 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, wxEmptyString); + } +} + +void EffectDistortion::UpdateControl(control id, bool enabled, wxString name) +{ + wxString suffix = _(" (Not Used):"); + switch (id) + { + case ID_Threshold: + /* i18n-hint: Control range. */ + if (enabled) suffix = _(" (-100 to 0 dB):"); + name += suffix; + + // Logarithmic slider is set indirectly + mThreshold = DB_TO_LINEAR(mParams.mThreshold_dB); + mThresholdS->SetValue((int) (mThreshold * SCL_Threshold_dB + 0.5)); + + mThresholdTxt->SetLabel(name); + mThresholdS->SetName(name); + mThresholdT->SetName(name); + mThresholdS->Enable(enabled); + mThresholdT->Enable(enabled); + break; + case ID_NoiseFloor: + /* i18n-hint: Control range. */ + if (enabled) suffix = _(" (-80 to -20 dB):"); + name += suffix; + + mNoiseFloorTxt->SetLabel(name); + mNoiseFloorS->SetName(name); + mNoiseFloorT->SetName(name); + mNoiseFloorS->Enable(enabled); + mNoiseFloorT->Enable(enabled); + break; + case ID_Param1: + /* i18n-hint: Control range. */ + if (enabled) suffix = _(" (0 to 100):"); + name += suffix; + + mParam1Txt->SetLabel(name); + mParam1S->SetName(name); + mParam1T->SetName(name); + mParam1S->Enable(enabled); + mParam1T->Enable(enabled); + break; + case ID_Param2: + /* i18n-hint: Control range. */ + if (enabled) suffix = _(" (0 to 100):"); + name += suffix; + + mParam2Txt->SetLabel(name); + mParam2S->SetName(name); + mParam2T->SetName(name); + mParam2S->Enable(enabled); + mParam2T->Enable(enabled); + break; + case ID_Repeats: + /* i18n-hint: Control range. */ + if (enabled) suffix = _(" (0 to 5):"); + name += suffix; + + mRepeatsTxt->SetLabel(name); + mRepeatsS->SetName(name); + mRepeatsT->SetName(name); + 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() == wxEmptyString) + textCtrl->SetValue(string); + else + string = textCtrl->GetValue(); + } + else { + if (textCtrl->GetValue() != wxEmptyString) + 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 i = 0; i < numPasses; i++) { + // 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(wxMin(index, 2 * STEPS - 1), 0); + xOffset = ((1 + sample) * STEPS) - index; + xOffset = wxMin(wxMax(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()); +} diff --git a/src/effects/Distortion.h b/src/effects/Distortion.h new file mode 100644 index 000000000..9e84a3d2c --- /dev/null +++ b/src/effects/Distortion.h @@ -0,0 +1,218 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + Distortion.h + + Steve Daulton + +**********************************************************************/ + +#ifndef __AUDACITY_EFFECT_DISTORTION__ +#define __AUDACITY_EFFECT_DISTORTION__ + +#include +#include +#include +#include +#include + +#include + +#include "Effect.h" + +class ShuttleGui; + +#define DISTORTION_PLUGIN_SYMBOL XO("Distortion") +#define STEPS 1024 // number of +ve or -ve steps in lookup tabe +#define TABLESIZE 2049 // size of lookup table (steps * 2 + 1) + +class EffectDistortionState +{ +public: + float samplerate; + sampleCount skipcount; + int tablechoiceindx; + bool dcblock; + double threshold; + double noisefloor; + double param1; + double param2; + int repeats; + + // DC block filter variables + std::queue queuesamples; + double queuetotal; +}; + +WX_DECLARE_OBJARRAY(EffectDistortionState, EffectDistortionStateArray); + +class EffectDistortion final : public Effect +{ +public: + EffectDistortion(); + virtual ~EffectDistortion(); + + struct Params + { + int mTableChoiceIndx; + bool mDCBlock; + double mThreshold_dB; + double mNoiseFloor; + double mParam1; + double mParam2; + int mRepeats; + }; + + // IdentInterface implementation + + wxString GetSymbol() override; + wxString GetDescription() override; + + // EffectIdentInterface implementation + + EffectType GetType() override; + bool SupportsRealtime() override; + + // EffectClientInterface implementation + + int GetAudioInCount() override; + int GetAudioOutCount() override; + bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) override; + sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) override; + bool RealtimeInitialize() override; + bool RealtimeAddProcessor(int numChannels, float sampleRate) override; + bool RealtimeFinalize() override; + sampleCount RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples) override; + bool GetAutomationParameters(EffectAutomationParameters & parms) override; + bool SetAutomationParameters(EffectAutomationParameters & parms) override; + wxArrayString GetFactoryPresets() override; + bool LoadFactoryPreset(int id) override; + + // Effect implementation + + void PopulateOrExchange(ShuttleGui & S) override; + bool TransferDataToWindow() override; + bool TransferDataFromWindow() override; + +private: + + enum control + { + ID_Type = 10000, + ID_DCBlock, + ID_Threshold, + ID_NoiseFloor, + ID_Param1, + ID_Param2, + ID_Repeats, + }; + // EffectDistortion implementation + + void InstanceInit(EffectDistortionState & data, float sampleRate); + sampleCount InstanceProcess(EffectDistortionState & data, + float **inBlock, + float **outBlock, + sampleCount blockLen); + + // Control Handlers + + void OnTypeChoice(wxCommandEvent & evt); + void OnDCBlockCheckbox(wxCommandEvent & evt); + void OnThresholdText(wxCommandEvent & evt); + void OnThresholdSlider(wxCommandEvent & evt); + void OnNoiseFloorText(wxCommandEvent & evt); + void OnNoiseFloorSlider(wxCommandEvent & evt); + void OnParam1Text(wxCommandEvent & evt); + void OnParam1Slider(wxCommandEvent & evt); + void OnParam2Text(wxCommandEvent & evt); + void OnParam2Slider(wxCommandEvent & evt); + void OnRepeatsText(wxCommandEvent & evt); + void OnRepeatsSlider(wxCommandEvent & evt); + void UpdateUI(); + void UpdateControl(control id, bool enable, wxString name); + void UpdateControlText(wxTextCtrl *textCtrl, wxString &string, bool enabled); + + void MakeTable(); + float WaveShaper(float sample); + float DCFilter(EffectDistortionState & data, float sample); + + // Preset tables for gain lookup + + void HardClip(); // hard clipping + void SoftClip(); // soft clipping + void ExponentialTable(); // exponential mapping + void LogarithmicTable(); // logarithmic mapping + void HalfSinTable(); + void CubicTable(); + void EvenHarmonicTable(); + void SineTable(); + void Leveller(); // 'Leveller' wavetable is modeled on the legacy effect of the same name. + void Rectifier(); // 0% = Dry, 50% = half-wave rectified, 100% = full-wave rectified (abs value). + void HardLimiter(); // Same effect as the LADSPA "hardLimiter 1413" + + // Wavetable helper functions + + void CopyHalfTable(); // for symmetric tables + + // Used by Soft Clipping but could be used for other tables. + // Log curve formula: y = T + (((e^(RT - Rx)) - 1) / -R) + // where R is the ratio, T is the threshold, and x is from T to 1. + inline float LogCurve(double threshold, float value, double ratio); + + // Used by Cubic curve but could be used for other tables + // Cubic formula: y = x - (x^3 / 3.0) + inline double Cubic(double x); + + +private: + EffectDistortionState mMaster; + EffectDistortionStateArray mSlaves; + + double mTable[TABLESIZE]; + double mThreshold; + bool mbSavedFilterState; + + // mMakeupGain is used by some distortion types to pass the + // amount of gain required to bring overall effect gain to unity + double mMakeupGain; + + int mTypChoiceIndex; + wxArrayString mTableTypes; + + wxChoice *mTypeChoiceCtrl; + wxTextCtrl *mThresholdT; + wxTextCtrl *mNoiseFloorT; + wxTextCtrl *mParam1T; + wxTextCtrl *mParam2T; + wxTextCtrl *mRepeatsT; + + wxSlider *mThresholdS; + wxSlider *mNoiseFloorS; + wxSlider *mParam1S; + wxSlider *mParam2S; + wxSlider *mRepeatsS; + + wxCheckBox *mDCBlockCheckBox; + + wxStaticText *mThresholdTxt; + wxStaticText *mNoiseFloorTxt; + wxStaticText *mParam1Txt; + wxStaticText *mParam2Txt; + wxStaticText *mRepeatsTxt; + + wxString mOldThresholdTxt; + wxString mOldmNoiseFloorTxt; + wxString mOldParam1Txt; + wxString mOldParam2Txt; + wxString mOldRepeatsTxt; + + Params mParams; + + DECLARE_EVENT_TABLE(); +}; + +#endif diff --git a/src/effects/LoadEffects.cpp b/src/effects/LoadEffects.cpp index 111efa264..6410b27ba 100644 --- a/src/effects/LoadEffects.cpp +++ b/src/effects/LoadEffects.cpp @@ -23,6 +23,7 @@ #include "ChangeSpeed.h" #include "ClickRemoval.h" #include "Compressor.h" +#include "Distortion.h" #include "DtmfGen.h" #include "Echo.h" #include "Paulstretch.h" @@ -116,6 +117,7 @@ EFFECT( CHANGESPEED, EffectChangeSpeed, () ) \ EFFECT( CLICKREMOVAL, EffectClickRemoval, () ) \ EFFECT( COMPRESSOR, EffectCompressor, () ) \ + EFFECT( DISTORTION, EffectDistortion, () ) \ EFFECT( ECHO, EffectEcho, () ) \ EFFECT( EQUALIZATION, EffectEqualization, () ) \ EFFECT( FADEIN, EffectFade, (true) ) \