/********************************************************************** Audacity: A Digital Audio Editor NoiseReduction.cpp Dominic Mazzoni detailed rewriting by Paul Licameli *******************************************************************//** \class EffectNoiseReduction \brief A two-pass effect to reduce background noise. The first pass is done over just noise. For each windowed sample of the sound, we take a FFT and then statistics are tabulated for each frequency band. During the noise reduction phase, we start by setting a gain control for each frequency band such that if the sound has exceeded the previously-determined threshold, the gain is set to 0 dB, otherwise the gain is set lower (e.g. -18 dB), to suppress the noise. Then time-smoothing is applied so that the gain for each frequency band moves slowly, and then frequency-smoothing is applied so that a single frequency is never suppressed or boosted in isolation. Lookahead is employed; this effect is not designed for real-time but if it were, there would be a significant delay. The gain controls are applied to the complex FFT of the signal, and then the inverse FFT is applied. A Hann window may be applied (depending on the advanced window types setting), and then the output signal is then pieced together using overlap/add. *//****************************************************************//** */ #include "../Audacity.h" #include "NoiseReduction.h" #include "LoadEffects.h" #include "EffectManager.h" #include "EffectUI.h" #include "../ShuttleGui.h" #include "../widgets/HelpSystem.h" #include "../Prefs.h" #include "../RealFFTf.h" #include "../WaveTrack.h" #include "../widgets/AudacityMessageBox.h" #include "../widgets/valnum.h" #include #include #include #if defined(__WXMSW__) && !defined(__CYGWIN__) #include #define finite(x) _finite(x) #endif #include #include #include #include #include #include #include #include // SPECTRAL_SELECTION not to affect this effect for now, as there might be no indication that it does. // [Discussed and agreed for v2.1 by Steve, Paul, Bill]. #undef EXPERIMENTAL_SPECTRAL_EDITING typedef std::vector FloatVector; // Define both of these to make the radio button three-way #define RESIDUE_CHOICE //#define ISOLATE_CHOICE // Define for Attack and release controls. // #define ATTACK_AND_RELEASE // Define to expose other advanced, experimental dialog controls //#define ADVANCED_SETTINGS // Define to make the old statistical methods an available choice //#define OLD_METHOD_AVAILABLE namespace { enum DiscriminationMethod { DM_MEDIAN, DM_SECOND_GREATEST, DM_OLD_METHOD, DM_N_METHODS, DM_DEFAULT_METHOD = DM_SECOND_GREATEST, }; const struct DiscriminationMethodInfo { const TranslatableString name; } discriminationMethodInfo[DM_N_METHODS] = { // Experimental only, don't need translations { XO("Median") }, { XO("Second greatest") }, { XO("Old") }, }; // magic number used only in the old statistics // and the old discrimination const float minSignalTime = 0.05f; enum WindowTypes { WT_RECTANGULAR_HANN = 0, // 2.0.6 behavior, requires 1/2 step WT_HANN_RECTANGULAR, // requires 1/2 step WT_HANN_HANN, // requires 1/4 step WT_BLACKMAN_HANN, // requires 1/4 step WT_HAMMING_RECTANGULAR, // requires 1/2 step WT_HAMMING_HANN, // requires 1/4 step WT_HAMMING_INV_HAMMING, // requires 1/2 step WT_N_WINDOW_TYPES, WT_DEFAULT_WINDOW_TYPES = WT_HANN_HANN }; const struct WindowTypesInfo { const TranslatableString name; unsigned minSteps; double inCoefficients[3]; double outCoefficients[3]; double productConstantTerm; } windowTypesInfo [WT_N_WINDOW_TYPES] = { // In all of these cases (but the last), the constant term of the product of windows // is the product of the windows' two constant terms, // plus one half the product of the first cosine coefficients. // Experimental only, don't need translations { XO("none, Hann (2.0.6 behavior)"), 2, { 1, 0, 0 }, { 0.5, -0.5, 0 }, 0.5 }, { XO("Hann, none"), 2, { 0.5, -0.5, 0 }, { 1, 0, 0 }, 0.5 }, { XO("Hann, Hann (default)"), 4, { 0.5, -0.5, 0 }, { 0.5, -0.5, 0 }, 0.375 }, { XO("Blackman, Hann"), 4, { 0.42, -0.5, 0.08 }, { 0.5, -0.5, 0 }, 0.335 }, { XO("Hamming, none"), 2, { 0.54, -0.46, 0.0 }, { 1, 0, 0 }, 0.54 }, { XO("Hamming, Hann"), 4, { 0.54, -0.46, 0.0 }, { 0.5, -0.5, 0 }, 0.385 }, { XO("Hamming, Reciprocal Hamming"), 2, { 0.54, -0.46, 0.0 }, { 1, 0, 0 }, 1.0 }, // output window is special }; enum { DEFAULT_WINDOW_SIZE_CHOICE = 8, // corresponds to 2048 DEFAULT_STEPS_PER_WINDOW_CHOICE = 1 // corresponds to 4, minimum for WT_HANN_HANN }; enum NoiseReductionChoice { NRC_REDUCE_NOISE, NRC_ISOLATE_NOISE, NRC_LEAVE_RESIDUE, }; } // namespace //---------------------------------------------------------------------------- // EffectNoiseReduction::Statistics //---------------------------------------------------------------------------- class EffectNoiseReduction::Statistics { public: Statistics(size_t spectrumSize, double rate, int windowTypes) : mRate(rate) , mWindowSize((spectrumSize - 1) * 2) , mWindowTypes(windowTypes) , mTotalWindows(0) , mTrackWindows(0) , mSums(spectrumSize) , mMeans(spectrumSize) #ifdef OLD_METHOD_AVAILABLE , mNoiseThreshold(spectrumSize) #endif {} // Noise profile statistics follow double mRate; // Rate of profile track(s) -- processed tracks must match size_t mWindowSize; int mWindowTypes; int mTotalWindows; int mTrackWindows; FloatVector mSums; FloatVector mMeans; #ifdef OLD_METHOD_AVAILABLE // Old statistics: FloatVector mNoiseThreshold; #endif }; //---------------------------------------------------------------------------- // EffectNoiseReduction::Settings //---------------------------------------------------------------------------- // This object is the memory of the effect between uses // (other than noise profile statistics) class EffectNoiseReduction::Settings { public: Settings(); ~Settings() {} bool PromptUser(EffectNoiseReduction *effect, wxWindow &parent, bool bHasProfile, bool bAllowTwiddleSettings); bool PrefsIO(bool read); bool Validate(EffectNoiseReduction *effect) const; size_t WindowSize() const { return 1u << (3 + mWindowSizeChoice); } unsigned StepsPerWindow() const { return 1u << (1 + mStepsPerWindowChoice); } bool mDoProfile; // Stored in preferences: // Basic: double mNewSensitivity; // - log10 of a probability... yeah. double mFreqSmoothingBands; // really an integer double mNoiseGain; // in dB, positive double mAttackTime; // in secs double mReleaseTime; // in secs // Advanced: double mOldSensitivity; // in dB, plus or minus // Basic: int mNoiseReductionChoice; // Advanced: int mWindowTypes; int mWindowSizeChoice; int mStepsPerWindowChoice; int mMethod; }; EffectNoiseReduction::Settings::Settings() : mDoProfile(true) { PrefsIO(true); } //---------------------------------------------------------------------------- // EffectNoiseReduction::Worker //---------------------------------------------------------------------------- // This object holds information needed only during effect calculation class EffectNoiseReduction::Worker { public: typedef EffectNoiseReduction::Settings Settings; typedef EffectNoiseReduction::Statistics Statistics; Worker(const Settings &settings, double sampleRate #ifdef EXPERIMENTAL_SPECTRAL_EDITING , double f0, double f1 #endif ); ~Worker(); bool Process(EffectNoiseReduction &effect, Statistics &statistics, WaveTrackFactory &factory, TrackList &tracks, double mT0, double mT1); private: bool ProcessOne(EffectNoiseReduction &effect, Statistics &statistics, WaveTrackFactory &factory, int count, WaveTrack *track, sampleCount start, sampleCount len); void StartNewTrack(); void ProcessSamples(Statistics &statistics, WaveTrack *outputTrack, size_t len, float *buffer); void FillFirstHistoryWindow(); void ApplyFreqSmoothing(FloatVector &gains); void GatherStatistics(Statistics &statistics); inline bool Classify(const Statistics &statistics, int band); void ReduceNoise(const Statistics &statistics, WaveTrack *outputTrack); void RotateHistoryWindows(); void FinishTrackStatistics(Statistics &statistics); void FinishTrack(Statistics &statistics, WaveTrack *outputTrack); private: const bool mDoProfile; const double mSampleRate; const size_t mWindowSize; // These have that size: HFFT hFFT; FloatVector mFFTBuffer; FloatVector mInWaveBuffer; FloatVector mOutOverlapBuffer; // These have that size, or 0: FloatVector mInWindow; FloatVector mOutWindow; const size_t mSpectrumSize; FloatVector mFreqSmoothingScratch; const size_t mFreqSmoothingBins; // When spectral selection limits the affected band: int mBinLow; // inclusive lower bound int mBinHigh; // exclusive upper bound const int mNoiseReductionChoice; const unsigned mStepsPerWindow; const size_t mStepSize; const int mMethod; const double mNewSensitivity; sampleCount mInSampleCount; sampleCount mOutStepCount; int mInWavePos; float mOneBlockAttack; float mOneBlockRelease; float mNoiseAttenFactor; float mOldSensitivityFactor; unsigned mNWindowsToExamine; unsigned mCenter; unsigned mHistoryLen; struct Record { Record(size_t spectrumSize) : mSpectrums(spectrumSize) , mGains(spectrumSize) , mRealFFTs(spectrumSize - 1) , mImagFFTs(spectrumSize - 1) { } FloatVector mSpectrums; FloatVector mGains; FloatVector mRealFFTs; FloatVector mImagFFTs; }; std::vector> mQueue; }; /****************************************************************//** \class EffectNoiseReduction::Dialog \brief Dialog used with EffectNoiseReduction **//*****************************************************************/ //---------------------------------------------------------------------------- // EffectNoiseReduction::Dialog //---------------------------------------------------------------------------- class EffectNoiseReduction::Dialog final : public EffectDialog { public: // constructors and destructors Dialog (EffectNoiseReduction *effect, Settings *settings, wxWindow *parent, bool bHasProfile, bool bAllowTwiddleSettings); void PopulateOrExchange(ShuttleGui & S) override; bool TransferDataToWindow() override; bool TransferDataFromWindow() override; const Settings &GetTempSettings() const { return mTempSettings; } private: void DisableControlsIfIsolating(); #ifdef ADVANCED_SETTINGS void EnableDisableSensitivityControls(); #endif // handlers void OnGetProfile( wxCommandEvent &event ); void OnNoiseReductionChoice( wxCommandEvent &event ); #ifdef ADVANCED_SETTINGS void OnMethodChoice(wxCommandEvent &); #endif void OnPreview(wxCommandEvent &event) override; void OnReduceNoise( wxCommandEvent &event ); void OnCancel( wxCommandEvent &event ); void OnHelp( wxCommandEvent &event ); void OnText(wxCommandEvent &event); void OnSlider(wxCommandEvent &event); // data members EffectNoiseReduction *m_pEffect; EffectNoiseReduction::Settings *m_pSettings; EffectNoiseReduction::Settings mTempSettings; bool mbHasProfile; bool mbAllowTwiddleSettings; wxRadioButton *mKeepSignal; #ifdef ISOLATE_CHOICE wxRadioButton *mKeepNoise; #endif #ifdef RESIDUE_CHOICE wxRadioButton *mResidue; #endif private: DECLARE_EVENT_TABLE() }; const ComponentInterfaceSymbol EffectNoiseReduction::Symbol { XO("Noise Reduction") }; namespace{ BuiltinEffectsModule::Registration< EffectNoiseReduction > reg; } EffectNoiseReduction::EffectNoiseReduction() : mSettings(std::make_unique()) { Init(); } EffectNoiseReduction::~EffectNoiseReduction() { } // ComponentInterface implementation ComponentInterfaceSymbol EffectNoiseReduction::GetSymbol() { return Symbol; } TranslatableString EffectNoiseReduction::GetDescription() { return XO("Removes background noise such as fans, tape noise, or hums"); } // EffectDefinitionInterface implementation EffectType EffectNoiseReduction::GetType() { return EffectTypeProcess; } bool EffectNoiseReduction::Init() { return true; } bool EffectNoiseReduction::CheckWhetherSkipEffect() { return false; } bool EffectNoiseReduction::ShowInterface( wxWindow &parent, const EffectDialogFactory &, bool forceModal) { // to do: use forceModal correctly // Doesn't use the factory but substitutes its own dialog // We may want to twiddle the levels if we are setting // from an automation dialog return mSettings->PromptUser(this, parent, bool(mStatistics), forceModal); } bool EffectNoiseReduction::Settings::PromptUser (EffectNoiseReduction *effect, wxWindow &parent, bool bHasProfile, bool bAllowTwiddleSettings) { EffectNoiseReduction::Dialog dlog (effect, this, &parent, bHasProfile, bAllowTwiddleSettings); dlog.CentreOnParent(); dlog.ShowModal(); if (dlog.GetReturnCode() == 0) return false; *this = dlog.GetTempSettings(); mDoProfile = (dlog.GetReturnCode() == 1); return PrefsIO(false); } namespace { template struct PrefsTableEntry { typedef FieldType (StructureType::*MemberPointer); MemberPointer field; const wxChar *name; FieldType defaultValue; }; template void readPrefs( StructureType *structure, const wxString &prefix, const PrefsTableEntry *fields, int numFields) { for (int ii = 0; ii < numFields; ++ii) { const PrefsTableEntry &entry = fields[ii]; gPrefs->Read(prefix + entry.name, &(structure->*(entry.field)), entry.defaultValue); } } template void writePrefs( StructureType *structure, const wxString &prefix, const PrefsTableEntry *fields, int numFields) { for (int ii = 0; ii < numFields; ++ii) { const PrefsTableEntry &entry = fields[ii]; gPrefs->Write(prefix + entry.name, structure->*(entry.field)); } } } bool EffectNoiseReduction::Settings::PrefsIO(bool read) { static const double DEFAULT_OLD_SENSITIVITY = 0.0; static const PrefsTableEntry doubleTable[] = { { &Settings::mNewSensitivity, wxT("Sensitivity"), 6.0 }, { &Settings::mNoiseGain, wxT("Gain"), 12.0 }, { &Settings::mAttackTime, wxT("AttackTime"), 0.02 }, { &Settings::mReleaseTime, wxT("ReleaseTime"), 0.10 }, { &Settings::mFreqSmoothingBands, wxT("FreqSmoothing"), 3.0 }, // Advanced settings { &Settings::mOldSensitivity, wxT("OldSensitivity"), DEFAULT_OLD_SENSITIVITY }, }; static int doubleTableSize = sizeof(doubleTable) / sizeof(doubleTable[0]); static const PrefsTableEntry intTable[] = { { &Settings::mNoiseReductionChoice, wxT("ReductionChoice"), NRC_REDUCE_NOISE }, // Advanced settings { &Settings::mWindowTypes, wxT("WindowTypes"), WT_DEFAULT_WINDOW_TYPES }, { &Settings::mWindowSizeChoice, wxT("WindowSize"), DEFAULT_WINDOW_SIZE_CHOICE }, { &Settings::mStepsPerWindowChoice, wxT("StepsPerWindow"), DEFAULT_STEPS_PER_WINDOW_CHOICE }, { &Settings::mMethod, wxT("Method"), DM_DEFAULT_METHOD }, }; static int intTableSize = sizeof(intTable) / sizeof(intTable[0]); static const wxString prefix(wxT("/Effects/NoiseReduction/")); if (read) { readPrefs(this, prefix, doubleTable, doubleTableSize); readPrefs(this, prefix, intTable, intTableSize); // Ignore preferences for unavailable options. #ifndef RESIDUE_CHOICE if (mNoiseReductionChoice == NRC_LEAVE_RESIDUE) mNoiseReductionChoice = NRC_ISOLATE_NOISE; #endif #ifndef ADVANCED_SETTINGS // Initialize all hidden advanced settings to defaults. mWindowTypes = WT_DEFAULT_WINDOW_TYPES; mWindowSizeChoice = DEFAULT_WINDOW_SIZE_CHOICE; mStepsPerWindowChoice = DEFAULT_STEPS_PER_WINDOW_CHOICE; mMethod = DM_DEFAULT_METHOD; mOldSensitivity = DEFAULT_OLD_SENSITIVITY; #endif #ifndef OLD_METHOD_AVAILABLE if (mMethod == DM_OLD_METHOD) mMethod = DM_DEFAULT_METHOD; #endif return true; } else { writePrefs(this, prefix, doubleTable, doubleTableSize); writePrefs(this, prefix, intTable, intTableSize); return gPrefs->Flush(); } } bool EffectNoiseReduction::Settings::Validate(EffectNoiseReduction *effect) const { if (StepsPerWindow() < windowTypesInfo[mWindowTypes].minSteps) { effect->Effect::MessageBox( XO("Steps per block are too few for the window types.") ); return false; } if (StepsPerWindow() > WindowSize()) { effect->Effect::MessageBox( XO("Steps per block cannot exceed the window size.") ); return false; } if (mMethod == DM_MEDIAN && StepsPerWindow() > 4) { effect->Effect::MessageBox( XO( "Median method is not implemented for more than four steps per window.") ); return false; } return true; } bool EffectNoiseReduction::Process() { // This same code will either reduce noise or profile it this->CopyInputTracks(); // Set up mOutputTracks. auto track = * (mOutputTracks->Selected< const WaveTrack >()).begin(); if (!track) return false; // Initialize statistics if gathering them, or check for mismatched (advanced) // settings if reducing noise. if (mSettings->mDoProfile) { size_t spectrumSize = 1 + mSettings->WindowSize() / 2; mStatistics = std::make_unique (spectrumSize, track->GetRate(), mSettings->mWindowTypes); } else if (mStatistics->mWindowSize != mSettings->WindowSize()) { // possible only with advanced settings ::Effect::MessageBox( XO("You must specify the same window size for steps 1 and 2.") ); return false; } else if (mStatistics->mWindowTypes != mSettings->mWindowTypes) { // A warning only ::Effect::MessageBox( XO("Warning: window types are not the same as for profiling.") ); } Worker worker(*mSettings, mStatistics->mRate #ifdef EXPERIMENTAL_SPECTRAL_EDITING , mF0, mF1 #endif ); bool bGoodResult = worker.Process (*this, *mStatistics, *mFactory, *mOutputTracks, mT0, mT1); if (mSettings->mDoProfile) { if (bGoodResult) mSettings->mDoProfile = false; // So that "repeat last effect" will reduce noise else mStatistics.reset(); // So that profiling must be done again before noise reduction } this->ReplaceProcessedTracks(bGoodResult); return bGoodResult; } EffectNoiseReduction::Worker::~Worker() { } bool EffectNoiseReduction::Worker::Process (EffectNoiseReduction &effect, Statistics &statistics, WaveTrackFactory &factory, TrackList &tracks, double inT0, double inT1) { int count = 0; for ( auto track : tracks.Selected< WaveTrack >() ) { if (track->GetRate() != mSampleRate) { if (mDoProfile) effect.Effect::MessageBox( XO("All noise profile data must have the same sample rate.") ); else effect.Effect::MessageBox( XO( "The sample rate of the noise profile must match that of the sound to be processed.") ); return false; } double trackStart = track->GetStartTime(); double trackEnd = track->GetEndTime(); double t0 = std::max(trackStart, inT0); double t1 = std::min(trackEnd, inT1); if (t1 > t0) { auto start = track->TimeToLongSamples(t0); auto end = track->TimeToLongSamples(t1); auto len = end - start; if (!ProcessOne(effect, statistics, factory, count, track, start, len)) return false; } ++count; } if (mDoProfile) { if (statistics.mTotalWindows == 0) { effect.Effect::MessageBox( XO("Selected noise profile is too short.") ); return false; } } return true; } void EffectNoiseReduction::Worker::ApplyFreqSmoothing(FloatVector &gains) { // Given an array of gain mutipliers, average them // GEOMETRICALLY. Don't multiply and take nth root -- // that may quickly cause underflows. Instead, average the logs. if (mFreqSmoothingBins == 0) return; { float *pScratch = &mFreqSmoothingScratch[0]; std::fill(pScratch, pScratch + mSpectrumSize, 0.0f); } for (size_t ii = 0; ii < mSpectrumSize; ++ii) gains[ii] = log(gains[ii]); // ii must be signed for (int ii = 0; ii < (int)mSpectrumSize; ++ii) { const int j0 = std::max(0, ii - (int)mFreqSmoothingBins); const int j1 = std::min(mSpectrumSize - 1, ii + mFreqSmoothingBins); for(int jj = j0; jj <= j1; ++jj) { mFreqSmoothingScratch[ii] += gains[jj]; } mFreqSmoothingScratch[ii] /= (j1 - j0 + 1); } for (size_t ii = 0; ii < mSpectrumSize; ++ii) gains[ii] = exp(mFreqSmoothingScratch[ii]); } EffectNoiseReduction::Worker::Worker (const Settings &settings, double sampleRate #ifdef EXPERIMENTAL_SPECTRAL_EDITING , double f0, double f1 #endif ) : mDoProfile(settings.mDoProfile) , mSampleRate(sampleRate) , mWindowSize(settings.WindowSize()) , hFFT(GetFFT(mWindowSize)) , mFFTBuffer(mWindowSize) , mInWaveBuffer(mWindowSize) , mOutOverlapBuffer(mWindowSize) , mInWindow() , mOutWindow() , mSpectrumSize(1 + mWindowSize / 2) , mFreqSmoothingScratch(mSpectrumSize) , mFreqSmoothingBins((int)(settings.mFreqSmoothingBands)) , mBinLow(0) , mBinHigh(mSpectrumSize) , mNoiseReductionChoice(settings.mNoiseReductionChoice) , mStepsPerWindow(settings.StepsPerWindow()) , mStepSize(mWindowSize / mStepsPerWindow) , mMethod(settings.mMethod) // Sensitivity setting is a base 10 log, turn it into a natural log , mNewSensitivity(settings.mNewSensitivity * log(10.0)) , mInSampleCount(0) , mOutStepCount(0) , mInWavePos(0) { #ifdef EXPERIMENTAL_SPECTRAL_EDITING { const double bin = mSampleRate / mWindowSize; if (f0 >= 0.0 ) mBinLow = floor(f0 / bin); if (f1 >= 0.0) mBinHigh = ceil(f1 / bin); } #endif const double noiseGain = -settings.mNoiseGain; const unsigned nAttackBlocks = 1 + (int)(settings.mAttackTime * sampleRate / mStepSize); const unsigned nReleaseBlocks = 1 + (int)(settings.mReleaseTime * sampleRate / mStepSize); // Applies to amplitudes, divide by 20: mNoiseAttenFactor = DB_TO_LINEAR(noiseGain); // Apply to gain factors which apply to amplitudes, divide by 20: mOneBlockAttack = DB_TO_LINEAR(noiseGain / nAttackBlocks); mOneBlockRelease = DB_TO_LINEAR(noiseGain / nReleaseBlocks); // Applies to power, divide by 10: mOldSensitivityFactor = pow(10.0, settings.mOldSensitivity / 10.0); mNWindowsToExamine = (mMethod == DM_OLD_METHOD) ? std::max(2, (int)(minSignalTime * sampleRate / mStepSize)) : 1 + mStepsPerWindow; mCenter = mNWindowsToExamine / 2; wxASSERT(mCenter >= 1); // release depends on this assumption if (mDoProfile) #ifdef OLD_METHOD_AVAILABLE mHistoryLen = mNWindowsToExamine; #else mHistoryLen = 1; #endif else { // Allow long enough queue for sufficient inspection of the middle // and for attack processing // See ReduceNoise() mHistoryLen = std::max(mNWindowsToExamine, mCenter + nAttackBlocks); } mQueue.resize(mHistoryLen); for (unsigned ii = 0; ii < mHistoryLen; ++ii) mQueue[ii] = std::make_unique(mSpectrumSize); // Create windows const double constantTerm = windowTypesInfo[settings.mWindowTypes].productConstantTerm; // One or the other window must by multiplied by this to correct for // overlap. Must scale down as steps get smaller, and overlaps larger. const double multiplier = 1.0 / (constantTerm * mStepsPerWindow); // Create the analysis window switch (settings.mWindowTypes) { case WT_RECTANGULAR_HANN: break; default: { const bool rectangularOut = settings.mWindowTypes == WT_HAMMING_RECTANGULAR || settings.mWindowTypes == WT_HANN_RECTANGULAR; const double m = rectangularOut ? multiplier : 1; const double *const coefficients = windowTypesInfo[settings.mWindowTypes].inCoefficients; const double c0 = coefficients[0]; const double c1 = coefficients[1]; const double c2 = coefficients[2]; mInWindow.resize(mWindowSize); for (size_t ii = 0; ii < mWindowSize; ++ii) mInWindow[ii] = m * (c0 + c1 * cos((2.0*M_PI*ii) / mWindowSize) + c2 * cos((4.0*M_PI*ii) / mWindowSize)); } break; } if (!mDoProfile) { // Create the synthesis window switch (settings.mWindowTypes) { case WT_HANN_RECTANGULAR: case WT_HAMMING_RECTANGULAR: break; case WT_HAMMING_INV_HAMMING: { mOutWindow.resize(mWindowSize); for (size_t ii = 0; ii < mWindowSize; ++ii) mOutWindow[ii] = multiplier / mInWindow[ii]; } break; default: { const double *const coefficients = windowTypesInfo[settings.mWindowTypes].outCoefficients; const double c0 = coefficients[0]; const double c1 = coefficients[1]; const double c2 = coefficients[2]; mOutWindow.resize(mWindowSize); for (size_t ii = 0; ii < mWindowSize; ++ii) mOutWindow[ii] = multiplier * (c0 + c1 * cos((2.0 * M_PI * ii) / mWindowSize) + c2 * cos((4.0 * M_PI * ii) / mWindowSize)); } break; } } } void EffectNoiseReduction::Worker::StartNewTrack() { float *pFill; for(unsigned ii = 0; ii < mHistoryLen; ++ii) { Record &record = *mQueue[ii]; pFill = &record.mSpectrums[0]; std::fill(pFill, pFill + mSpectrumSize, 0.0f); pFill = &record.mRealFFTs[0]; std::fill(pFill, pFill + mSpectrumSize - 1, 0.0f); pFill = &record.mImagFFTs[0]; std::fill(pFill, pFill + mSpectrumSize - 1, 0.0f); pFill = &record.mGains[0]; std::fill(pFill, pFill + mSpectrumSize, mNoiseAttenFactor); } pFill = &mOutOverlapBuffer[0]; std::fill(pFill, pFill + mWindowSize, 0.0f); pFill = &mInWaveBuffer[0]; std::fill(pFill, pFill + mWindowSize, 0.0f); if (mDoProfile) { // We do not want leading zero padded windows mInWavePos = 0; mOutStepCount = -(int)(mHistoryLen - 1); } else { // So that the queue gets primed with some windows, // zero-padded in front, the first having mStepSize // samples of wave data: mInWavePos = mWindowSize - mStepSize; // This starts negative, to count up until the queue fills: mOutStepCount = -(int)(mHistoryLen - 1) // ... and then must pass over the padded windows, // before the first full window: - (int)(mStepsPerWindow - 1); } mInSampleCount = 0; } void EffectNoiseReduction::Worker::ProcessSamples (Statistics &statistics, WaveTrack *outputTrack, size_t len, float *buffer) { while (len && mOutStepCount * mStepSize < mInSampleCount) { auto avail = std::min(len, mWindowSize - mInWavePos); memmove(&mInWaveBuffer[mInWavePos], buffer, avail * sizeof(float)); buffer += avail; len -= avail; mInWavePos += avail; if (mInWavePos == (int)mWindowSize) { FillFirstHistoryWindow(); if (mDoProfile) GatherStatistics(statistics); else ReduceNoise(statistics, outputTrack); ++mOutStepCount; RotateHistoryWindows(); // Rotate for overlap-add memmove(&mInWaveBuffer[0], &mInWaveBuffer[mStepSize], (mWindowSize - mStepSize) * sizeof(float)); mInWavePos -= mStepSize; } } } void EffectNoiseReduction::Worker::FillFirstHistoryWindow() { // Transform samples to frequency domain, windowed as needed if (mInWindow.size() > 0) for (size_t ii = 0; ii < mWindowSize; ++ii) mFFTBuffer[ii] = mInWaveBuffer[ii] * mInWindow[ii]; else memmove(&mFFTBuffer[0], &mInWaveBuffer[0], mWindowSize * sizeof(float)); RealFFTf(&mFFTBuffer[0], hFFT.get()); Record &record = *mQueue[0]; // Store real and imaginary parts for later inverse FFT, and compute // power { float *pReal = &record.mRealFFTs[1]; float *pImag = &record.mImagFFTs[1]; float *pPower = &record.mSpectrums[1]; int *pBitReversed = &hFFT->BitReversed[1]; const auto last = mSpectrumSize - 1; for (unsigned int ii = 1; ii < last; ++ii) { const int kk = *pBitReversed++; const float realPart = *pReal++ = mFFTBuffer[kk]; const float imagPart = *pImag++ = mFFTBuffer[kk + 1]; *pPower++ = realPart * realPart + imagPart * imagPart; } // DC and Fs/2 bins need to be handled specially const float dc = mFFTBuffer[0]; record.mRealFFTs[0] = dc; record.mSpectrums[0] = dc*dc; const float nyquist = mFFTBuffer[1]; record.mImagFFTs[0] = nyquist; // For Fs/2, not really imaginary record.mSpectrums[last] = nyquist * nyquist; } if (mNoiseReductionChoice != NRC_ISOLATE_NOISE) { // Default all gains to the reduction factor, // until we decide to raise some of them later float *pGain = &record.mGains[0]; std::fill(pGain, pGain + mSpectrumSize, mNoiseAttenFactor); } } void EffectNoiseReduction::Worker::RotateHistoryWindows() { std::rotate(mQueue.begin(), mQueue.end() - 1, mQueue.end()); } void EffectNoiseReduction::Worker::FinishTrackStatistics(Statistics &statistics) { const int windows = statistics.mTrackWindows; const int multiplier = statistics.mTotalWindows; const int denom = windows + multiplier; // Combine averages in case of multiple profile tracks. if (windows) for (int ii = 0, nn = statistics.mMeans.size(); ii < nn; ++ii) { float &mean = statistics.mMeans[ii]; float &sum = statistics.mSums[ii]; mean = (mean * multiplier + sum) / denom; // Reset for next track sum = 0; } // Reset for next track statistics.mTrackWindows = 0; statistics.mTotalWindows = denom; } void EffectNoiseReduction::Worker::FinishTrack (Statistics &statistics, WaveTrack *outputTrack) { // Keep flushing empty input buffers through the history // windows until we've output exactly as many samples as // were input. // Well, not exactly, but not more than one step-size of extra samples // at the end. // We'll DELETE them later in ProcessOne. FloatVector empty(mStepSize); while (mOutStepCount * mStepSize < mInSampleCount) { ProcessSamples(statistics, outputTrack, mStepSize, &empty[0]); } } void EffectNoiseReduction::Worker::GatherStatistics(Statistics &statistics) { ++statistics.mTrackWindows; { // NEW statistics const float *pPower = &mQueue[0]->mSpectrums[0]; float *pSum = &statistics.mSums[0]; for (size_t jj = 0; jj < mSpectrumSize; ++jj) { *pSum++ += *pPower++; } } #ifdef OLD_METHOD_AVAILABLE // The noise threshold for each frequency is the maximum // level achieved at that frequency for a minimum of // mMinSignalBlocks blocks in a row - the max of a min. auto finish = mHistoryLen; { // old statistics const float *pPower = &mQueue[0]->mSpectrums[0]; float *pThreshold = &statistics.mNoiseThreshold[0]; for (int jj = 0; jj < mSpectrumSize; ++jj) { float min = *pPower++; for (unsigned ii = 1; ii < finish; ++ii) min = std::min(min, mQueue[ii]->mSpectrums[jj]); *pThreshold = std::max(*pThreshold, min); ++pThreshold; } } #endif } // Return true iff the given band of the "center" window looks like noise. // Examine the band in a few neighboring windows to decide. inline bool EffectNoiseReduction::Worker::Classify(const Statistics &statistics, int band) { switch (mMethod) { #ifdef OLD_METHOD_AVAILABLE case DM_OLD_METHOD: { float min = mQueue[0]->mSpectrums[band]; for (unsigned ii = 1; ii < mNWindowsToExamine; ++ii) min = std::min(min, mQueue[ii]->mSpectrums[band]); return min <= mOldSensitivityFactor * statistics.mNoiseThreshold[band]; } #endif // New methods suppose an exponential distribution of power values // in the noise; NEW sensitivity is meant to be log of probability // that noise strays above the threshold. Call that probability // 1 - F. The quantile function of an exponential distribution is // log (1 - F) * mean. Thus simply multiply mean by sensitivity // to get the threshold. case DM_MEDIAN: // This method examines the window and all windows // that partly overlap it, and takes a median, to // avoid being fooled by up and down excursions into // either the mistake of classifying noise as not noise // (leaving a musical noise chime), or the opposite // (distorting the signal with a drop out). if (mNWindowsToExamine == 3) // No different from second greatest. goto secondGreatest; else if (mNWindowsToExamine == 5) { float greatest = 0.0, second = 0.0, third = 0.0; for (unsigned ii = 0; ii < mNWindowsToExamine; ++ii) { const float power = mQueue[ii]->mSpectrums[band]; if (power >= greatest) third = second, second = greatest, greatest = power; else if (power >= second) third = second, second = power; else if (power >= third) third = power; } return third <= mNewSensitivity * statistics.mMeans[band]; } else { wxASSERT(false); return true; } secondGreatest: case DM_SECOND_GREATEST: { // This method just throws out the high outlier. It // should be less prone to distortions and more prone to // chimes. float greatest = 0.0, second = 0.0; for (unsigned ii = 0; ii < mNWindowsToExamine; ++ii) { const float power = mQueue[ii]->mSpectrums[band]; if (power >= greatest) second = greatest, greatest = power; else if (power >= second) second = power; } return second <= mNewSensitivity * statistics.mMeans[band]; } default: wxASSERT(false); return true; } } void EffectNoiseReduction::Worker::ReduceNoise (const Statistics &statistics, WaveTrack *outputTrack) { // Raise the gain for elements in the center of the sliding history // or, if isolating noise, zero out the non-noise { float *pGain = &mQueue[mCenter]->mGains[0]; if (mNoiseReductionChoice == NRC_ISOLATE_NOISE) { // All above or below the selected frequency range is non-noise std::fill(pGain, pGain + mBinLow, 0.0f); std::fill(pGain + mBinHigh, pGain + mSpectrumSize, 0.0f); pGain += mBinLow; for (int jj = mBinLow; jj < mBinHigh; ++jj) { const bool isNoise = Classify(statistics, jj); *pGain++ = isNoise ? 1.0 : 0.0; } } else { // All above or below the selected frequency range is non-noise std::fill(pGain, pGain + mBinLow, 1.0f); std::fill(pGain + mBinHigh, pGain + mSpectrumSize, 1.0f); pGain += mBinLow; for (int jj = mBinLow; jj < mBinHigh; ++jj) { const bool isNoise = Classify(statistics, jj); if (!isNoise) *pGain = 1.0; ++pGain; } } } if (mNoiseReductionChoice != NRC_ISOLATE_NOISE) { // In each direction, define an exponential decay of gain from the // center; make actual gains the maximum of mNoiseAttenFactor, and // the decay curve, and their prior values. // First, the attack, which goes backward in time, which is, // toward higher indices in the queue. for (size_t jj = 0; jj < mSpectrumSize; ++jj) { for (unsigned ii = mCenter + 1; ii < mHistoryLen; ++ii) { const float minimum = std::max(mNoiseAttenFactor, mQueue[ii - 1]->mGains[jj] * mOneBlockAttack); float &gain = mQueue[ii]->mGains[jj]; if (gain < minimum) gain = minimum; else // We can stop now, our attack curve is intersecting // the decay curve of some window previously processed. break; } } // Now, release. We need only look one window ahead. This part will // be visited again when we examine the next window, and // carry the decay further. { float *pNextGain = &mQueue[mCenter - 1]->mGains[0]; const float *pThisGain = &mQueue[mCenter]->mGains[0]; for (int nn = mSpectrumSize; nn--;) { *pNextGain = std::max(*pNextGain, std::max(mNoiseAttenFactor, *pThisGain++ * mOneBlockRelease)); ++pNextGain; } } } if (mOutStepCount >= -(int)(mStepsPerWindow - 1)) { Record &record = *mQueue[mHistoryLen - 1]; // end of the queue const auto last = mSpectrumSize - 1; if (mNoiseReductionChoice != NRC_ISOLATE_NOISE) // Apply frequency smoothing to output gain // Gains are not less than mNoiseAttenFactor ApplyFreqSmoothing(record.mGains); // Apply gain to FFT { const float *pGain = &record.mGains[1]; const float *pReal = &record.mRealFFTs[1]; const float *pImag = &record.mImagFFTs[1]; float *pBuffer = &mFFTBuffer[2]; auto nn = mSpectrumSize - 2; if (mNoiseReductionChoice == NRC_LEAVE_RESIDUE) { for (; nn--;) { // Subtract the gain we would otherwise apply from 1, and // negate that to flip the phase. const double gain = *pGain++ - 1.0; *pBuffer++ = *pReal++ * gain; *pBuffer++ = *pImag++ * gain; } mFFTBuffer[0] = record.mRealFFTs[0] * (record.mGains[0] - 1.0); // The Fs/2 component is stored as the imaginary part of the DC component mFFTBuffer[1] = record.mImagFFTs[0] * (record.mGains[last] - 1.0); } else { for (; nn--;) { const double gain = *pGain++; *pBuffer++ = *pReal++ * gain; *pBuffer++ = *pImag++ * gain; } mFFTBuffer[0] = record.mRealFFTs[0] * record.mGains[0]; // The Fs/2 component is stored as the imaginary part of the DC component mFFTBuffer[1] = record.mImagFFTs[0] * record.mGains[last]; } } // Invert the FFT into the output buffer InverseRealFFTf(&mFFTBuffer[0], hFFT.get()); // Overlap-add if (mOutWindow.size() > 0) { float *pOut = &mOutOverlapBuffer[0]; float *pWindow = &mOutWindow[0]; int *pBitReversed = &hFFT->BitReversed[0]; for (unsigned int jj = 0; jj < last; ++jj) { int kk = *pBitReversed++; *pOut++ += mFFTBuffer[kk] * (*pWindow++); *pOut++ += mFFTBuffer[kk + 1] * (*pWindow++); } } else { float *pOut = &mOutOverlapBuffer[0]; int *pBitReversed = &hFFT->BitReversed[0]; for (unsigned int jj = 0; jj < last; ++jj) { int kk = *pBitReversed++; *pOut++ += mFFTBuffer[kk]; *pOut++ += mFFTBuffer[kk + 1]; } } float *buffer = &mOutOverlapBuffer[0]; if (mOutStepCount >= 0) { // Output the first portion of the overlap buffer, they're done outputTrack->Append((samplePtr)buffer, floatSample, mStepSize); } // Shift the remainder over. memmove(buffer, buffer + mStepSize, sizeof(float) * (mWindowSize - mStepSize)); std::fill(buffer + mWindowSize - mStepSize, buffer + mWindowSize, 0.0f); } } bool EffectNoiseReduction::Worker::ProcessOne (EffectNoiseReduction &effect, Statistics &statistics, WaveTrackFactory &factory, int count, WaveTrack * track, sampleCount start, sampleCount len) { if (track == NULL) return false; StartNewTrack(); WaveTrack::Holder outputTrack; if(!mDoProfile) outputTrack = track->EmptyCopy(); auto bufferSize = track->GetMaxBlockSize(); FloatVector buffer(bufferSize); bool bLoopSuccess = true; auto samplePos = start; while (bLoopSuccess && samplePos < start + len) { //Get a blockSize of samples (smaller than the size of the buffer) const auto blockSize = limitSampleBufferSize( track->GetBestBlockSize(samplePos), start + len - samplePos ); //Get the samples from the track and put them in the buffer track->Get((samplePtr)&buffer[0], floatSample, samplePos, blockSize); samplePos += blockSize; mInSampleCount += blockSize; ProcessSamples(statistics, outputTrack.get(), blockSize, &buffer[0]); // Update the Progress meter, let user cancel bLoopSuccess = !effect.TrackProgress(count, ( samplePos - start ).as_double() / len.as_double() ); } if (bLoopSuccess) { if (mDoProfile) FinishTrackStatistics(statistics); else FinishTrack(statistics, &*outputTrack); } if (bLoopSuccess && !mDoProfile) { // Flush the output WaveTrack (since it's buffered) outputTrack->Flush(); // Take the output track and insert it in place of the original // sample data (as operated on -- this may not match mT0/mT1) double t0 = outputTrack->LongSamplesToTime(start); double tLen = outputTrack->LongSamplesToTime(len); // Filtering effects always end up with more data than they started with. Delete this 'tail'. outputTrack->HandleClear(tLen, outputTrack->GetEndTime(), false, false); track->ClearAndPaste(t0, t0 + tLen, &*outputTrack, true, false); } return bLoopSuccess; } //---------------------------------------------------------------------------- // EffectNoiseReduction::Dialog //---------------------------------------------------------------------------- enum { ID_BUTTON_GETPROFILE = 10001, ID_RADIOBUTTON_KEEPSIGNAL, #ifdef ISOLATE_CHOICE ID_RADIOBUTTON_KEEPNOISE, #endif #ifdef RESIDUE_CHOICE ID_RADIOBUTTON_RESIDUE, #endif #ifdef ADVANCED_SETTINGS ID_CHOICE_METHOD, #endif // Slider/text pairs ID_GAIN_SLIDER, ID_GAIN_TEXT, ID_NEW_SENSITIVITY_SLIDER, ID_NEW_SENSITIVITY_TEXT, #ifdef ATTACK_AND_RELEASE ID_ATTACK_TIME_SLIDER, ID_ATTACK_TIME_TEXT, ID_RELEASE_TIME_SLIDER, ID_RELEASE_TIME_TEXT, #endif ID_FREQ_SLIDER, ID_FREQ_TEXT, END_OF_BASIC_SLIDERS, #ifdef ADVANCED_SETTINGS ID_OLD_SENSITIVITY_SLIDER = END_OF_BASIC_SLIDERS, ID_OLD_SENSITIVITY_TEXT, END_OF_ADVANCED_SLIDERS, END_OF_SLIDERS = END_OF_ADVANCED_SLIDERS, #else END_OF_SLIDERS = END_OF_BASIC_SLIDERS, #endif FIRST_SLIDER = ID_GAIN_SLIDER, }; namespace { struct ControlInfo { typedef double (EffectNoiseReduction::Settings::*MemberPointer); double Value(long sliderSetting) const { return valueMin + (double(sliderSetting) / sliderMax) * (valueMax - valueMin); } long SliderSetting(double value) const { return TrapLong( 0.5 + sliderMax * (value - valueMin) / (valueMax - valueMin), 0, sliderMax); } wxString Text(double value) const { if (formatAsInt) return wxString::Format(format, (int)(value)); else return wxString::Format(format, value); } void CreateControls(int id, ShuttleGui &S) const { wxTextCtrl *const text = S.Id(id + 1) .Validator>( formatAsInt ? 0 : 2, nullptr, NumValidatorStyle::DEFAULT, valueMin, valueMax ) .AddTextBox(textBoxCaption, wxT(""), 0); wxSlider *const slider = S.Id(id) .Name( sliderName ) .Style(wxSL_HORIZONTAL) .MinSize( { 150, -1 } ) .AddSlider( {}, 0, sliderMax); } MemberPointer field; double valueMin; double valueMax; long sliderMax; // (valueMin - valueMax) / sliderMax is the value increment of the slider const wxChar* format; bool formatAsInt; const TranslatableString textBoxCaption; const TranslatableString sliderName; ControlInfo(MemberPointer f, double vMin, double vMax, long sMax, const wxChar* fmt, bool fAsInt, const TranslatableString &caption, const TranslatableString &name) : field(f), valueMin(vMin), valueMax(vMax), sliderMax(sMax), format(fmt), formatAsInt(fAsInt) , textBoxCaption(caption), sliderName(name) { } }; const ControlInfo *controlInfo() { static const ControlInfo table[] = { ControlInfo(&EffectNoiseReduction::Settings::mNoiseGain, 0.0, 48.0, 48, wxT("%d"), true, XXO("&Noise reduction (dB):"), XO("Noise reduction")), ControlInfo(&EffectNoiseReduction::Settings::mNewSensitivity, 0.0, 24.0, 48, wxT("%.2f"), false, XXO("&Sensitivity:"), XO("Sensitivity")), #ifdef ATTACK_AND_RELEASE ControlInfo(&EffectNoiseReduction::Settings::mAttackTime, 0, 1.0, 100, wxT("%.2f"), false, XXO("Attac&k time (secs):"), XO("Attack time")), ControlInfo(&EffectNoiseReduction::Settings::mReleaseTime, 0, 1.0, 100, wxT("%.2f"), false, XXO("R&elease time (secs):"), XO("Release time")), #endif ControlInfo(&EffectNoiseReduction::Settings::mFreqSmoothingBands, 0, 12, 12, wxT("%d"), true, XXO("&Frequency smoothing (bands):"), XO("Frequency smoothing")), #ifdef ADVANCED_SETTINGS ControlInfo(&EffectNoiseReduction::Settings::mOldSensitivity, -20.0, 20.0, 4000, wxT("%.2f"), false, XXO("Sensiti&vity (dB):"), XO("Old Sensitivity")), // add here #endif }; return table; } } // namespace BEGIN_EVENT_TABLE(EffectNoiseReduction::Dialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, EffectNoiseReduction::Dialog::OnReduceNoise) EVT_BUTTON(wxID_CANCEL, EffectNoiseReduction::Dialog::OnCancel) EVT_BUTTON(ID_EFFECT_PREVIEW, EffectNoiseReduction::Dialog::OnPreview) EVT_BUTTON(ID_BUTTON_GETPROFILE, EffectNoiseReduction::Dialog::OnGetProfile) EVT_BUTTON(wxID_HELP, EffectNoiseReduction::Dialog::OnHelp) EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPSIGNAL, EffectNoiseReduction::Dialog::OnNoiseReductionChoice) #ifdef ISOLATE_CHOICE EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPNOISE, EffectNoiseReduction::Dialog::OnNoiseReductionChoice) #endif #ifdef RESIDUE_CHOICE EVT_RADIOBUTTON(ID_RADIOBUTTON_RESIDUE, EffectNoiseReduction::Dialog::OnNoiseReductionChoice) #endif #ifdef ADVANCED_SETTINGS EVT_CHOICE(ID_CHOICE_METHOD, EffectNoiseReduction::Dialog::OnMethodChoice) #endif EVT_SLIDER(ID_GAIN_SLIDER, EffectNoiseReduction::Dialog::OnSlider) EVT_TEXT(ID_GAIN_TEXT, EffectNoiseReduction::Dialog::OnText) EVT_SLIDER(ID_NEW_SENSITIVITY_SLIDER, EffectNoiseReduction::Dialog::OnSlider) EVT_TEXT(ID_NEW_SENSITIVITY_TEXT, EffectNoiseReduction::Dialog::OnText) EVT_SLIDER(ID_FREQ_SLIDER, EffectNoiseReduction::Dialog::OnSlider) EVT_TEXT(ID_FREQ_TEXT, EffectNoiseReduction::Dialog::OnText) #ifdef ATTACK_AND_RELEASE EVT_SLIDER(ID_ATTACK_TIME_SLIDER, EffectNoiseReduction::Dialog::OnSlider) EVT_TEXT(ID_ATTACK_TIME_TEXT, EffectNoiseReduction::Dialog::OnText) EVT_SLIDER(ID_RELEASE_TIME_SLIDER, EffectNoiseReduction::Dialog::OnSlider) EVT_TEXT(ID_RELEASE_TIME_TEXT, EffectNoiseReduction::Dialog::OnText) #endif #ifdef ADVANCED_SETTINGS EVT_SLIDER(ID_OLD_SENSITIVITY_SLIDER, EffectNoiseReduction::Dialog::OnSlider) EVT_TEXT(ID_OLD_SENSITIVITY_TEXT, EffectNoiseReduction::Dialog::OnText) #endif END_EVENT_TABLE() EffectNoiseReduction::Dialog::Dialog (EffectNoiseReduction *effect, EffectNoiseReduction::Settings *settings, wxWindow *parent, bool bHasProfile, bool bAllowTwiddleSettings) : EffectDialog( parent, XO("Noise Reduction"), EffectTypeProcess,wxDEFAULT_DIALOG_STYLE, eHelpButton ) , m_pEffect(effect) , m_pSettings(settings) // point to , mTempSettings(*settings) // copy , mbHasProfile(bHasProfile) , mbAllowTwiddleSettings(bAllowTwiddleSettings) // NULL out the control members until the controls are created. , mKeepSignal(NULL) #ifdef ISOLATE_CHOICE , mKeepNoise(NULL) #endif #ifdef RESIDUE_CHOICE , mResidue(NULL) #endif { EffectDialog::Init(); wxButton *const pButtonPreview = (wxButton *)wxWindow::FindWindowById(ID_EFFECT_PREVIEW, this); wxButton *const pButtonReduceNoise = (wxButton *)wxWindow::FindWindowById(wxID_OK, this); if (mbHasProfile || mbAllowTwiddleSettings) { pButtonPreview->Enable(!mbAllowTwiddleSettings); pButtonReduceNoise->SetFocus(); } else { pButtonPreview->Enable(false); pButtonReduceNoise->Enable(false); } } void EffectNoiseReduction::Dialog::DisableControlsIfIsolating() { // If Isolate is chosen, disable controls that define // "what to do with noise" rather than "what is noise." // Else, enable them. // This does NOT include sensitivity, NEW or old, nor // the choice of window functions, size, or step. // The method choice is not included, because it affects // which sensitivity slider is operative, and that is part // of what defines noise. static const int toDisable[] = { ID_GAIN_SLIDER, ID_GAIN_TEXT, ID_FREQ_SLIDER, ID_FREQ_TEXT, #ifdef ATTACK_AND_RELEASE ID_ATTACK_TIME_SLIDER, ID_ATTACK_TIME_TEXT, ID_RELEASE_TIME_SLIDER, ID_RELEASE_TIME_TEXT, #endif }; static const int nToDisable = sizeof(toDisable) / sizeof(toDisable[0]); bool bIsolating = #ifdef ISOLATE_CHOICE mKeepNoise->GetValue(); #else false; #endif for (int ii = nToDisable; ii--;) wxWindow::FindWindowById(toDisable[ii], this)->Enable(!bIsolating); } #ifdef ADVANCED_SETTINGS void EffectNoiseReduction::Dialog::EnableDisableSensitivityControls() { wxChoice *const pChoice = static_cast(wxWindow::FindWindowById(ID_CHOICE_METHOD, this)); const bool bOldMethod = pChoice->GetSelection() == DM_OLD_METHOD; wxWindow::FindWindowById(ID_OLD_SENSITIVITY_SLIDER, this)->Enable(bOldMethod); wxWindow::FindWindowById(ID_OLD_SENSITIVITY_TEXT, this)->Enable(bOldMethod); wxWindow::FindWindowById(ID_NEW_SENSITIVITY_SLIDER, this)->Enable(!bOldMethod); wxWindow::FindWindowById(ID_NEW_SENSITIVITY_TEXT, this)->Enable(!bOldMethod); } #endif void EffectNoiseReduction::Dialog::OnGetProfile(wxCommandEvent & WXUNUSED(event)) { // Project has not be changed so skip pushing state EffectManager::Get().SetSkipStateFlag(true); if (!TransferDataFromWindow()) return; // Return code distinguishes this first step from the actual effect EndModal(1); } // This handles the whole radio group void EffectNoiseReduction::Dialog::OnNoiseReductionChoice( wxCommandEvent & WXUNUSED(event)) { if (mKeepSignal->GetValue()) mTempSettings.mNoiseReductionChoice = NRC_REDUCE_NOISE; #ifdef ISOLATE_CHOICE else if (mKeepNoise->GetValue()) mTempSettings.mNoiseReductionChoice = NRC_ISOLATE_NOISE; #endif #ifdef RESIDUE_CHOICE else mTempSettings.mNoiseReductionChoice = NRC_LEAVE_RESIDUE; #endif DisableControlsIfIsolating(); } #ifdef ADVANCED_SETTINGS void EffectNoiseReduction::Dialog::OnMethodChoice(wxCommandEvent &) { EnableDisableSensitivityControls(); } #endif void EffectNoiseReduction::Dialog::OnPreview(wxCommandEvent & WXUNUSED(event)) { if (!TransferDataFromWindow()) return; // Save & restore parameters around Preview, because we didn't do OK. auto cleanup = valueRestorer( *m_pSettings ); *m_pSettings = mTempSettings; m_pSettings->mDoProfile = false; m_pEffect->Preview( false ); } void EffectNoiseReduction::Dialog::OnReduceNoise( wxCommandEvent & WXUNUSED(event)) { if (!TransferDataFromWindow()) return; EndModal(2); } void EffectNoiseReduction::Dialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { EndModal(0); } void EffectNoiseReduction::Dialog::OnHelp(wxCommandEvent & WXUNUSED(event)) { HelpSystem::ShowHelp(this, "Noise_Reduction", true); } void EffectNoiseReduction::Dialog::PopulateOrExchange(ShuttleGui & S) { S.StartStatic(XO("Step 1")); { S.AddVariableText(XO( "Select a few seconds of just noise so Audacity knows what to filter out,\nthen click Get Noise Profile:")); //m_pButton_GetProfile = S.Id(ID_BUTTON_GETPROFILE).AddButton(XXO("&Get Noise Profile")); } S.EndStatic(); S.StartStatic(XO("Step 2")); { S.AddVariableText(XO( "Select all of the audio you want filtered, choose how much noise you want\nfiltered out, and then click 'OK' to reduce noise.\n")); S.StartMultiColumn(3, wxEXPAND); S.SetStretchyCol(2); { for (int id = FIRST_SLIDER; id < END_OF_BASIC_SLIDERS; id += 2) { const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2]; info.CreateControls(id, S); } } S.EndMultiColumn(); S.StartMultiColumn( 2 #ifdef RESIDUE_CHOICE +1 #endif #ifdef ISOLATE_CHOICE +1 #endif , wxALIGN_CENTER_HORIZONTAL); { S.AddPrompt(XXO("Noise:")); mKeepSignal = S.Id(ID_RADIOBUTTON_KEEPSIGNAL) /* i18n-hint: Translate differently from "Residue" ! */ .AddRadioButton(XXO("Re&duce")); #ifdef ISOLATE_CHOICE mKeepNoise = S.Id(ID_RADIOBUTTON_KEEPNOISE) .AddRadioButtonToGroup(XXO("&Isolate")); #endif #ifdef RESIDUE_CHOICE mResidue = S.Id(ID_RADIOBUTTON_RESIDUE) /* i18n-hint: Means the difference between effect and original sound. Translate differently from "Reduce" ! */ .AddRadioButtonToGroup(XXO("Resid&ue")); #endif } S.EndMultiColumn(); } S.EndStatic(); #ifdef ADVANCED_SETTINGS S.StartStatic(XO("Advanced Settings")); { S.StartMultiColumn(2); { S.TieChoice(XXO("&Window types:"), mTempSettings.mWindowTypes, []{ TranslatableStrings windowTypeChoices; for (int ii = 0; ii < WT_N_WINDOW_TYPES; ++ii) windowTypeChoices.push_back(windowTypesInfo[ii].name); return windowTypeChoices; }() ); S.TieChoice(XXO("Window si&ze:"), mTempSettings.mWindowSizeChoice, { XO("8") , XO("16") , XO("32") , XO("64") , XO("128") , XO("256") , XO("512") , XO("1024") , XO("2048 (default)") , XO("4096") , XO("8192") , XO("16384") , } ); S.TieChoice(XXO("S&teps per window:"), mTempSettings.mStepsPerWindowChoice, { XO("2") , XO("4 (default)") , XO("8") , XO("16") , XO("32") , XO("64") , } ); S.Id(ID_CHOICE_METHOD) .TieChoice(XXO("Discrimination &method:"), mTempSettings.mMethod, []{ TranslatableStrings methodChoices; int nn = DM_N_METHODS; #ifndef OLD_METHOD_AVAILABLE --nn; #endif for (int ii = 0; ii < nn; ++ii) methodChoices.push_back(discriminationMethodInfo[ii].name); return methodChoices; }()); } S.EndMultiColumn(); S.StartMultiColumn(3, wxEXPAND); S.SetStretchyCol(2); { for (int id = END_OF_BASIC_SLIDERS; id < END_OF_ADVANCED_SLIDERS; id += 2) { const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2]; info.CreateControls(id, S); } } S.EndMultiColumn(); } S.EndStatic(); #endif } bool EffectNoiseReduction::Dialog::TransferDataToWindow() { // Do the choice controls: if (!EffectDialog::TransferDataToWindow()) return false; for (int id = FIRST_SLIDER; id < END_OF_SLIDERS; id += 2) { wxSlider* slider = static_cast(wxWindow::FindWindowById(id, this)); wxTextCtrl* text = static_cast(wxWindow::FindWindowById(id + 1, this)); const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2]; const double field = mTempSettings.*(info.field); text->SetValue(info.Text(field)); slider->SetValue(info.SliderSetting(field)); } mKeepSignal->SetValue(mTempSettings.mNoiseReductionChoice == NRC_REDUCE_NOISE); #ifdef ISOLATE_CHOICE mKeepNoise->SetValue(mTempSettings.mNoiseReductionChoice == NRC_ISOLATE_NOISE); #endif #ifdef RESIDUE_CHOICE mResidue->SetValue(mTempSettings.mNoiseReductionChoice == NRC_LEAVE_RESIDUE); #endif // Set the enabled states of controls DisableControlsIfIsolating(); #ifdef ADVANCED_SETTINGS EnableDisableSensitivityControls(); #endif return true; } bool EffectNoiseReduction::Dialog::TransferDataFromWindow() { if( !wxWindow::Validate() ) return false; // Do the choice controls: if (!EffectDialog::TransferDataFromWindow()) return false; wxCommandEvent dummy; OnNoiseReductionChoice(dummy); return mTempSettings.Validate(m_pEffect); } void EffectNoiseReduction::Dialog::OnText(wxCommandEvent &event) { int id = event.GetId(); int idx = (id - FIRST_SLIDER - 1) / 2; const ControlInfo &info = controlInfo()[idx]; wxTextCtrl* text = static_cast(wxWindow::FindWindowById(id, this)); wxSlider* slider = static_cast(wxWindow::FindWindowById(id - 1, this)); double &field = mTempSettings.*(info.field); text->GetValue().ToDouble(&field); slider->SetValue(info.SliderSetting(field)); } void EffectNoiseReduction::Dialog::OnSlider(wxCommandEvent &event) { int id = event.GetId(); int idx = (id - FIRST_SLIDER) / 2; const ControlInfo &info = controlInfo()[idx]; wxSlider* slider = static_cast(wxWindow::FindWindowById(id, this)); wxTextCtrl* text = static_cast(wxWindow::FindWindowById(id + 1, this)); double &field = mTempSettings.*(info.field); field = info.Value(slider->GetValue()); text->SetValue(info.Text(field)); }