/********************************************************************** 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 #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 #include #include #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(&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>( 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>( 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>( 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>( 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>( &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().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(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()); }