Audacity 3.2.0
NoiseReduction.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NoiseReduction.cpp
6
7 Dominic Mazzoni
8
9 detailed rewriting by
10 Paul Licameli
11
12*******************************************************************//****************************************************************/
40#include "NoiseReduction.h"
41#include "EffectOutputTracks.h"
42
43#include "LoadEffects.h"
44#include "EffectManager.h"
45#include "EffectPreview.h"
46#include "EffectUI.h"
47
48#include "ShuttleGui.h"
49#include "HelpSystem.h"
50#include "FFT.h"
51#include "Prefs.h"
52#include "RealFFTf.h"
53#include "../SpectrumTransformer.h"
54
55#include "WaveTrack.h"
56#include "AudacityMessageBox.h"
57#include "../widgets/valnum.h"
58
59#include <algorithm>
60#include <vector>
61#include <math.h>
62
63#if defined(__WXMSW__) && !defined(__CYGWIN__)
64#include <float.h>
65#define finite(x) _finite(x)
66#endif
67
68#include <wx/button.h>
69#include <wx/choice.h>
70#include <wx/radiobut.h>
71#include <wx/slider.h>
72#include <wx/valtext.h>
73#include <wx/textctrl.h>
74
75// SPECTRAL_SELECTION not to affect this effect for now, as there might be no indication that it does.
76// [Discussed and agreed for v2.1 by Steve, Paul, Bill].
77#undef SPECTRAL_EDIT_NOISE_REDUCTION
78
79typedef std::vector<float> FloatVector;
80
81// Define both of these to make the radio button three-way
82#define RESIDUE_CHOICE
83//#define ISOLATE_CHOICE
84
85// Define for Attack and release controls.
86// #define ATTACK_AND_RELEASE
87
88// Define to expose other advanced, experimental dialog controls
89//#define ADVANCED_SETTINGS
90
91// Define to make the old statistical methods an available choice
92//#define OLD_METHOD_AVAILABLE
93
94namespace {
95
96enum DiscriminationMethod : size_t {
100
103};
104
108 // Experimental only, don't need translations
109 { XO("Median") },
110 { XO("Second greatest") },
111 { XO("Old") },
113
114// magic number used only in the old statistics
115// and the old discrimination
116const float minSignalTime = 0.05f;
117
118enum WindowTypes : unsigned {
119 WT_RECTANGULAR_HANN = 0, // 2.0.6 behavior, requires 1/2 step
120 WT_HANN_RECTANGULAR, // requires 1/2 step
121 WT_HANN_HANN, // requires 1/4 step
122 WT_BLACKMAN_HANN, // requires 1/4 step
123 WT_HAMMING_RECTANGULAR, // requires 1/2 step
124 WT_HAMMING_HANN, // requires 1/4 step
125 // WT_HAMMING_INV_HAMMING, // requires 1/2 step
126
130
131const struct WindowTypesInfo {
133 unsigned minSteps;
135
136 // Experimental only, don't need translations
137 { Verbatim("none, Hann (2.0.6 behavior)"), 2 },
138 /* i18n-hint: Hann is a proper name */
139 { Verbatim("Hann, none"), 2 },
140 /* i18n-hint: Hann is a proper name */
141 { Verbatim("Hann, Hann (default)"), 4 },
142 /* i18n-hint: Hann and Blackman are proper names */
143 { Verbatim("Blackman, Hann"), 4 },
144 /* i18n-hint: Hamming is a proper name */
145 { Verbatim("Hamming, none"), 2 },
146 /* i18n-hint: Hamming and Hann area proper names */
147 { Verbatim("Hamming, Hann"), 4 },
148 /* i18n-hint: Hamming is a proper name */
149 // { XO("Hamming, Reciprocal Hamming"), 2, }, // output window is special
151
152enum {
153 DEFAULT_WINDOW_SIZE_CHOICE = 8, // corresponds to 2048
154 DEFAULT_STEPS_PER_WINDOW_CHOICE = 1 // corresponds to 4, minimum for WT_HANN_HANN
156
161};
162
163} // namespace
164
165//----------------------------------------------------------------------------
166// EffectNoiseReduction::Statistics
167//----------------------------------------------------------------------------
168
170{
171public:
172 Statistics(size_t spectrumSize, double rate, int windowTypes)
173 : mRate{ rate }
174 , mWindowSize{ (spectrumSize - 1) * 2 }
175 , mWindowTypes{ windowTypes }
176 , mTotalWindows{ 0 }
177 , mTrackWindows{ 0 }
178 , mSums( spectrumSize )
179 , mMeans (spectrumSize )
180#ifdef OLD_METHOD_AVAILABLE
181 , mNoiseThreshold( spectrumSize )
182#endif
183 {}
184
185 // Noise profile statistics follow
186
187 double mRate; // Rate of profile track(s) -- processed tracks must match
190
195
196#ifdef OLD_METHOD_AVAILABLE
197 // Old statistics:
198 FloatVector mNoiseThreshold;
199#endif
200};
201
202//----------------------------------------------------------------------------
203// EffectNoiseReduction::Settings
204//----------------------------------------------------------------------------
205
206// This object is the memory of the effect between uses
207// (other than noise profile statistics)
209{
210public:
211 Settings();
213
215 wxWindow &parent, bool bHasProfile, bool bAllowTwiddleSettings);
216 bool PrefsIO(bool read);
217 bool Validate(EffectNoiseReduction *effect) const;
218
219 size_t WindowSize() const { return 1u << (3 + mWindowSizeChoice); }
220 unsigned StepsPerWindow() const { return 1u << (1 + mStepsPerWindowChoice); }
221 size_t SpectrumSize() const { return 1 + WindowSize() / 2; }
222 size_t StepSize() const { return WindowSize() / StepsPerWindow(); }
223
225
226 // Stored in preferences:
227
228 // Basic:
229 double mNewSensitivity; // - log10 of a probability... yeah.
230 double mFreqSmoothingBands; // really an integer
231 double mNoiseGain; // in dB, positive
232 double mAttackTime; // in secs
233 double mReleaseTime; // in secs
234
235 // Advanced:
236 double mOldSensitivity; // in dB, plus or minus
237
238 // Basic:
240
241 // Advanced:
246};
247
249 : mDoProfile{ true }
250{
251 PrefsIO(true);
252}
253
256 WaveChannel *pOutputTrack,
257 bool needsOutput, eWindowFunctions inWindowType,
258 eWindowFunctions outWindowType, size_t windowSize,
259 unsigned stepsPerWindow, bool leadingPadding, bool trailingPadding
260 ) : TrackSpectrumTransformer{ pOutputTrack,
261 needsOutput, inWindowType, outWindowType,
262 windowSize, stepsPerWindow, leadingPadding, trailingPadding
263 }
264 , mWorker{ worker }
265 {
266 }
267 struct MyWindow : public Window
268 {
269 explicit MyWindow(size_t windowSize)
270 : Window{ windowSize }
271 , mSpectrums(windowSize / 2 + 1)
272 , mGains(windowSize / 2 + 1)
273 {}
274 ~MyWindow() override;
275
278 };
279
280 MyWindow &NthWindow(int nn) { return static_cast<MyWindow&>(Nth(nn)); }
281 std::unique_ptr<Window> NewWindow(size_t windowSize) override;
282 bool DoStart() override;
283 bool DoFinish() override;
284
286};
287
288//----------------------------------------------------------------------------
289// EffectNoiseReduction::Worker
290//----------------------------------------------------------------------------
291
292// This object holds information needed only during effect calculation
294{
295public:
298
300 Statistics &statistics
301#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
302 , double f0, double f1
303#endif
304 );
305 ~Worker();
306
307 bool Process(eWindowFunctions inWindowType, eWindowFunctions outWindowType,
308 TrackList &tracks, double mT0, double mT1);
309
310 static bool Processor(SpectrumTransformer &transformer);
311
312 void ApplyFreqSmoothing(FloatVector &gains);
313 void GatherStatistics(MyTransformer &transformer);
314 inline bool Classify(
315 MyTransformer &transformer, unsigned nWindows, int band);
316 void ReduceNoise(MyTransformer &transformer);
318
319 const bool mDoProfile;
320
324
326 const size_t mFreqSmoothingBins;
327 // When spectral selection limits the affected band:
328 size_t mBinLow; // inclusive lower bound
329 size_t mBinHigh; // exclusive upper bound
330
332 const int mMethod;
333 const double mNewSensitivity;
334
339
341 unsigned mCenter;
342 unsigned mHistoryLen;
343
344 // Following are for progress indicator only:
348};
349
350/****************************************************************//*****************************************************************/
356
357//----------------------------------------------------------------------------
358// EffectNoiseReduction::Dialog
359//----------------------------------------------------------------------------
360
362{
363public:
364 // constructors and destructors
367 wxWindow *parent, bool bHasProfile,
368 bool bAllowTwiddleSettings);
369
370 void PopulateOrExchange(ShuttleGui & S) override;
371 bool TransferDataToWindow() override;
372 bool TransferDataFromWindow() override;
373
375 { return mTempSettings; }
376
377private:
379
380#ifdef ADVANCED_SETTINGS
381 void EnableDisableSensitivityControls();
382#endif
383
384 // handlers
385 void OnGetProfile( wxCommandEvent &event );
386 void OnNoiseReductionChoice( wxCommandEvent &event );
387#ifdef ADVANCED_SETTINGS
388 void OnMethodChoice(wxCommandEvent &);
389#endif
390 void OnPreview(wxCommandEvent &event) override;
391 void OnReduceNoise( wxCommandEvent &event );
392 void OnCancel( wxCommandEvent &event );
393 void OnHelp( wxCommandEvent &event );
394
395 void OnText(wxCommandEvent &event);
396 void OnSlider(wxCommandEvent &event);
397
398 // data members
399
405
408
409
410 wxRadioButton *mKeepSignal;
411#ifdef ISOLATE_CHOICE
412 wxRadioButton *mKeepNoise;
413#endif
414#ifdef RESIDUE_CHOICE
415 wxRadioButton *mResidue;
416#endif
417
418private:
419 DECLARE_EVENT_TABLE()
420};
421
423{ XO("Noise Reduction") };
424
426
428: mSettings(std::make_unique<EffectNoiseReduction::Settings>())
429{
430}
431
433{
434}
435
436// ComponentInterface implementation
437
439{
440 return Symbol;
441}
442
444{
445 return XO("Removes background noise such as fans, tape noise, or hums");
446}
447
448// EffectDefinitionInterface implementation
449
451{
452 return EffectTypeProcess;
453}
454
457
462 wxWindow &parent, const EffectDialogFactory &,
463 std::shared_ptr<EffectInstance> &pInstance, EffectSettingsAccess &access,
464 bool forceModal)
465{
466 // Assign the out parameter
467 pInstance = MakeInstance();
468
469 // to do: use forceModal correctly
470
471 // Doesn't use the factory but substitutes its own dialog
472
473 // We may want to twiddle the levels if we are setting
474 // from a macro editing dialog
475 return mSettings->PromptUser(this, access, parent,
477}
478
480 EffectSettingsAccess &access, wxWindow &parent,
481 bool bHasProfile, bool bAllowTwiddleSettings)
482{
483 EffectNoiseReduction::Dialog dlog(effect, access,
484 this, &parent, bHasProfile, bAllowTwiddleSettings);
485
486 dlog.CentreOnParent();
487 dlog.ShowModal();
488
489 const auto returnCode = dlog.GetReturnCode();
490 if (!returnCode)
491 return 0;
492
493 *this = dlog.GetTempSettings();
494 mDoProfile = (returnCode == 1);
495
496 if (!PrefsIO(false))
497 return 0;
498 return returnCode;
499}
500
501namespace {
502 template <typename StructureType, typename FieldType>
504 typedef FieldType (StructureType::*MemberPointer);
505
507 const wxChar *name;
508 FieldType defaultValue;
509 };
510
511 template <typename StructureType, typename FieldType>
513 StructureType *structure, const wxString &prefix,
514 const PrefsTableEntry<StructureType, FieldType> *fields, size_t numFields)
515 {
516 for (size_t ii = 0; ii < numFields; ++ii) {
518 gPrefs->Read(prefix + entry.name, &(structure->*(entry.field)),
519 entry.defaultValue);
520 }
521 }
522
523 template <typename StructureType, typename FieldType>
525 const StructureType *structure, const wxString &prefix,
526 const PrefsTableEntry<StructureType, FieldType> *fields, size_t numFields)
527 {
528 for (size_t ii = 0; ii < numFields; ++ii) {
530 gPrefs->Write(prefix + entry.name, structure->*(entry.field));
531 }
532 }
533}
534
536{
537 static const double DEFAULT_OLD_SENSITIVITY = 0.0;
538
539 static const PrefsTableEntry<Settings, double> doubleTable[] = {
540 { &Settings::mNewSensitivity, wxT("Sensitivity"), 6.0 },
541 { &Settings::mNoiseGain, wxT("Gain"), 6.0 },
542 { &Settings::mAttackTime, wxT("AttackTime"), 0.02 },
543 { &Settings::mReleaseTime, wxT("ReleaseTime"), 0.10 },
544 { &Settings::mFreqSmoothingBands, wxT("FreqSmoothing"), 6.0 },
545
546 // Advanced settings
547 { &Settings::mOldSensitivity, wxT("OldSensitivity"), DEFAULT_OLD_SENSITIVITY },
548 };
549 static auto doubleTableSize = sizeof(doubleTable) / sizeof(doubleTable[0]);
550
551 static const PrefsTableEntry<Settings, int> intTable[] = {
552 { &Settings::mNoiseReductionChoice, wxT("ReductionChoice"), NRC_REDUCE_NOISE },
553
554 // Advanced settings
558 { &Settings::mMethod, wxT("Method"), DM_DEFAULT_METHOD },
559 };
560 static auto intTableSize = sizeof(intTable) / sizeof(intTable[0]);
561
562 static const wxString prefix(wxT("/Effects/NoiseReduction/"));
563
564 if (read) {
565 readPrefs(this, prefix, doubleTable, doubleTableSize);
566 readPrefs(this, prefix, intTable, intTableSize);
567
568 // Ignore preferences for unavailable options.
569#if !(defined(RESIDUE_CHOICE) || defined (ISOLATE_CHOICE))
570 mNoiseReductionChoice == NRC_REDUCE_NOISE;
571#elif !(defined(RESIDUE_CHOICE))
572 if (mNoiseReductionChoice == NRC_LEAVE_RESIDUE)
573 mNoiseReductionChoice = NRC_ISOLATE_NOISE;
574#elif !(defined(ISOLATE_CHOICE))
575 if (mNoiseReductionChoice == NRC_ISOLATE_NOISE)
576 mNoiseReductionChoice = NRC_LEAVE_RESIDUE;
577#endif
578
579#ifndef ADVANCED_SETTINGS
580 // Initialize all hidden advanced settings to defaults.
581 mWindowTypes = WT_DEFAULT_WINDOW_TYPES;
582 mWindowSizeChoice = DEFAULT_WINDOW_SIZE_CHOICE;
583 mStepsPerWindowChoice = DEFAULT_STEPS_PER_WINDOW_CHOICE;
584 mMethod = DM_DEFAULT_METHOD;
585 mOldSensitivity = DEFAULT_OLD_SENSITIVITY;
586#endif
587
588#ifndef OLD_METHOD_AVAILABLE
589 if (mMethod == DM_OLD_METHOD)
590 mMethod = DM_DEFAULT_METHOD;
591#endif
592
593 return true;
594 }
595 else {
596 writePrefs(this, prefix, doubleTable, doubleTableSize);
597 writePrefs(this, prefix, intTable, intTableSize);
598 return gPrefs->Flush();
599 }
600}
601
603{
604 if (StepsPerWindow() < windowTypesInfo[mWindowTypes].minSteps) {
606 XO("Steps per block are too few for the window types.") );
607 return false;
608 }
609
610 if (StepsPerWindow() > WindowSize()) {
612 XO("Steps per block cannot exceed the window size.") );
613 return false;
614 }
615
616 if (mMethod == DM_MEDIAN && StepsPerWindow() > 4) {
618 XO(
619"Median method is not implemented for more than four steps per window.") );
620 return false;
621 }
622
623 return true;
624}
625
626auto MyTransformer::NewWindow(size_t windowSize)
627 -> std::unique_ptr<Window>
628{
629 return std::make_unique<MyWindow>(windowSize);
630}
631
633{
634}
635
637{
638 // This same code will either reduce noise or profile it
639
640 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
641
642 auto track = *(outputs.Get().Selected<const WaveTrack>()).begin();
643 if (!track)
644 return false;
645
646 const auto stepsPerWindow = mSettings->StepsPerWindow();
647 const auto stepSize = mSettings->WindowSize() / stepsPerWindow;
648
649 // Initialize statistics if gathering them, or check for mismatched (advanced)
650 // settings if reducing noise.
651 if (mSettings->mDoProfile) {
652 const auto spectrumSize = mSettings->SpectrumSize();
653 mStatistics = std::make_unique<Statistics>
654 (spectrumSize, track->GetRate(), mSettings->mWindowTypes);
655 }
656 else if (mStatistics->mWindowSize != mSettings->WindowSize()) {
657 // possible only with advanced settings
659 XO("You must specify the same window size for steps 1 and 2.") );
660 return false;
661 }
662 else if (mStatistics->mWindowTypes != mSettings->mWindowTypes) {
663 // A warning only
665 XO("Warning: window types are not the same as for profiling.") );
666 }
667
668 eWindowFunctions inWindowType, outWindowType;
669 switch (mSettings->mWindowTypes) {
671 inWindowType = eWinFuncRectangular;
672 outWindowType = eWinFuncHann;
673 break;
675 inWindowType = eWinFuncHann;
676 outWindowType = eWinFuncRectangular;
677 break;
678 case WT_BLACKMAN_HANN:
679 inWindowType = eWinFuncBlackman;
680 outWindowType = eWinFuncHann;
681 break;
683 inWindowType = eWinFuncHamming;
684 outWindowType = eWinFuncRectangular;
685 break;
686 case WT_HAMMING_HANN:
687 inWindowType = eWinFuncHamming;
688 outWindowType = eWinFuncHann;
689 break;
690 default:
691 wxASSERT(false);
692 [[fallthrough]] ;
693 case WT_HANN_HANN:
694 inWindowType = outWindowType = eWinFuncHann;
695 break;
696 }
697 Worker worker{ *this, *mSettings, *mStatistics
698#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
699 , mF0, mF1
700#endif
701 };
702 bool bGoodResult = worker.Process(inWindowType, outWindowType,
703 outputs.Get(), mT0, mT1);
704 const auto wasProfile = mSettings->mDoProfile;
705 if (mSettings->mDoProfile) {
706 if (bGoodResult)
707 mSettings->mDoProfile = false; // So that "repeat last effect" will reduce noise
708 else
709 mStatistics.reset(); // So that profiling must be done again before noise reduction
710 }
711 if (bGoodResult && !wasProfile)
712 outputs.Commit();
713
714 return bGoodResult;
715}
716
718{
719}
720
722 eWindowFunctions inWindowType, eWindowFunctions outWindowType,
723 TrackList &tracks, double inT0, double inT1)
724{
725 mProgressTrackCount = 0;
726 for (auto track : tracks.Selected<WaveTrack>()) {
727 mProgressWindowCount = 0;
728 if (track->GetRate() != mStatistics.mRate) {
729 if (mDoProfile)
731 XO("All noise profile data must have the same sample rate.") );
732 else
734 XO(
735"The sample rate of the noise profile must match that of the sound to be processed.") );
736 return false;
737 }
738
739 double trackStart = track->GetStartTime();
740 double trackEnd = track->GetEndTime();
741 double t0 = std::max(trackStart, inT0);
742 double t1 = std::min(trackEnd, inT1);
743
744 if (t1 > t0) {
745 auto start = track->TimeToLongSamples(t0);
746 auto end = track->TimeToLongSamples(t1);
747 const auto len = end - start;
748 mLen = len;
749 const auto extra =
750 (mSettings.StepsPerWindow() - 1) * mSettings.SpectrumSize();
751 // Adjust denominator for presence or absence of padding,
752 // which makes the number of windows visited either more or less
753 // than the number of window steps in the data.
754 if (mDoProfile)
755 mLen -= extra;
756 else
757 mLen += extra;
758
759 auto t0 = track->LongSamplesToTime(start);
760 auto tLen = track->LongSamplesToTime(len);
761 std::optional<WaveTrack::Holder> ppTempTrack;
762 std::optional<ChannelGroup::ChannelIterator<WaveChannel>> pIter;
763 WaveTrack *pFirstTrack{};
764 if (!mSettings.mDoProfile) {
765 ppTempTrack.emplace(track->EmptyCopy());
766 pFirstTrack = ppTempTrack->get();
767 pIter.emplace(pFirstTrack->Channels().begin());
768 }
769 for (const auto pChannel : track->Channels()) {
770 auto pOutputTrack = pIter ? *(*pIter)++ : nullptr;
771 MyTransformer transformer{ *this, pOutputTrack.get(),
772 !mSettings.mDoProfile, inWindowType, outWindowType,
773 mSettings.WindowSize(), mSettings.StepsPerWindow(),
774 !mSettings.mDoProfile, !mSettings.mDoProfile
775 };
776 if (!transformer
777 .Process(Processor, *pChannel, mHistoryLen, start, len))
778 return false;
779 ++mProgressTrackCount;
780 }
781 if (ppTempTrack) {
782 TrackSpectrumTransformer::PostProcess(*pFirstTrack, len);
783 constexpr auto preserveSplits = true;
784 constexpr auto merge = true;
785 track->ClearAndPaste(
786 t0, t0 + tLen, **ppTempTrack, preserveSplits, merge);
787 }
788 }
789 }
790
791 if (mDoProfile) {
792 if (mStatistics.mTotalWindows == 0) {
794 XO("Selected noise profile is too short."));
795 return false;
796 }
797 }
798
799 return true;
800}
801
803{
804 // Given an array of gain mutipliers, average them
805 // GEOMETRICALLY. Don't multiply and take nth root --
806 // that may quickly cause underflows. Instead, average the logs.
807
808 if (mFreqSmoothingBins == 0)
809 return;
810
811 const auto spectrumSize = mSettings.SpectrumSize();
812
813 {
814 auto pScratch = mFreqSmoothingScratch.data();
815 std::fill(pScratch, pScratch + spectrumSize, 0.0f);
816 }
817
818 for (size_t ii = 0; ii < spectrumSize; ++ii)
819 gains[ii] = log(gains[ii]);
820
821 // ii must be signed
822 for (int ii = 0; ii < (int)spectrumSize; ++ii) {
823 const int j0 = std::max(0, ii - (int)mFreqSmoothingBins);
824 const int j1 = std::min(spectrumSize - 1, ii + mFreqSmoothingBins);
825 for(int jj = j0; jj <= j1; ++jj) {
826 mFreqSmoothingScratch[ii] += gains[jj];
827 }
828 mFreqSmoothingScratch[ii] /= (j1 - j0 + 1);
829 }
830
831 for (size_t ii = 0; ii < spectrumSize; ++ii)
832 gains[ii] = exp(mFreqSmoothingScratch[ii]);
833}
834
836 const Settings &settings, Statistics &statistics
837#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
838 , double f0, double f1
839#endif
840)
841: mDoProfile{ settings.mDoProfile }
842
843, mEffect{ effect }
845, mStatistics{ statistics }
846
847, mFreqSmoothingScratch(mSettings.SpectrumSize())
848, mFreqSmoothingBins{ size_t(std::max(0.0, settings.mFreqSmoothingBands)) }
849, mBinLow{ 0 }
850, mBinHigh{ mSettings.SpectrumSize() }
851
852, mNoiseReductionChoice{ settings.mNoiseReductionChoice }
853, mMethod{ settings.mMethod }
854
855// Sensitivity setting is a base 10 log, turn it into a natural log
856, mNewSensitivity{ settings.mNewSensitivity * log(10.0) }
857{
858 const auto sampleRate = mStatistics.mRate;
859
860#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
861 {
862 // mBinLow is inclusive, mBinHigh is exclusive, of
863 // the range of frequencies to affect. Include any
864 // bin that partly overlaps the selected range of frequencies.
865 const double bin = sampleRate / mWindowSize;
866 if (f0 >= 0.0 )
867 mBinLow = floor(f0 / bin);
868 if (f1 >= 0.0)
869 mBinHigh = ceil(f1 / bin);
870 }
871#endif
872
873 const double noiseGain = -settings.mNoiseGain;
874 const unsigned nAttackBlocks =
876 const unsigned nReleaseBlocks =
878 // Applies to amplitudes, divide by 20:
879 mNoiseAttenFactor = DB_TO_LINEAR(noiseGain);
880 // Apply to gain factors which apply to amplitudes, divide by 20:
881 mOneBlockAttack = DB_TO_LINEAR(noiseGain / nAttackBlocks);
882 mOneBlockRelease = DB_TO_LINEAR(noiseGain / nReleaseBlocks);
883 // Applies to power, divide by 10:
885
887 ? std::max(2, (int)(minSignalTime * sampleRate / mSettings.StepSize()))
889
891 wxASSERT(mCenter >= 1); // release depends on this assumption
892
893 if (mDoProfile)
894#ifdef OLD_METHOD_AVAILABLE
896#else
897 mHistoryLen = 1;
898#endif
899 else {
900 // Allow long enough queue for sufficient inspection of the middle
901 // and for attack processing
902 // See ReduceNoise()
903 mHistoryLen = std::max(mNWindowsToExamine, mCenter + nAttackBlocks);
904 }
905}
906
908{
909 for (size_t ii = 0, nn = TotalQueueSize(); ii < nn; ++ii) {
910 MyWindow &record = NthWindow(ii);
911 std::fill(record.mSpectrums.begin(), record.mSpectrums.end(), 0.0);
912 std::fill(record.mGains.begin(), record.mGains.end(),
913 mWorker.mNoiseAttenFactor);
914 }
916}
917
919{
920 auto &transformer = static_cast<MyTransformer &>(trans);
921 auto &worker = transformer.mWorker;
922 // Compute power spectrum in the newest window
923 {
924 auto &record = transformer.NthWindow(0);
925 float *pSpectrum = &record.mSpectrums[0];
926 const double dc = record.mRealFFTs[0];
927 *pSpectrum++ = dc * dc;
928 float *pReal = &record.mRealFFTs[1], *pImag = &record.mImagFFTs[1];
929 for (size_t nn = worker.mSettings.SpectrumSize() - 2; nn--;) {
930 const double re = *pReal++, im = *pImag++;
931 *pSpectrum++ = re * re + im * im;
932 }
933 const double nyquist = record.mImagFFTs[0];
934 *pSpectrum = nyquist * nyquist;
935 }
936
937 if (worker.mDoProfile)
938 worker.GatherStatistics(transformer);
939 else
940 worker.ReduceNoise(transformer);
941
942 // Update the Progress meter, let user cancel
943 return !worker.mEffect.TrackProgress(worker.mProgressTrackCount,
944 std::min(1.0,
945 ((++worker.mProgressWindowCount).as_double() *
946 worker.mSettings.StepSize()) / worker.mLen.as_double()));
947}
948
950{
951 const auto windows = mStatistics.mTrackWindows;
952
953 // Combine averages in case of multiple profile tracks.
954 if (windows) {
955 const auto multiplier = mStatistics.mTotalWindows;
956 const auto denom = windows + multiplier;
957 for (size_t ii = 0, nn = mStatistics.mMeans.size(); ii < nn; ++ii) {
958 auto &mean = mStatistics.mMeans[ii];
959 auto &sum = mStatistics.mSums[ii];
960 mean = (mean * multiplier + sum) / denom;
961 // Reset for next track
962 sum = 0;
963 }
964 // Reset for next track
965 mStatistics.mTrackWindows = 0;
966 mStatistics.mTotalWindows = denom;
967 }
968}
969
971{
972 ++mStatistics.mTrackWindows;
973
974 {
975 // NEW statistics
976 auto pPower = transformer.NthWindow(0).mSpectrums.data();
977 auto pSum = mStatistics.mSums.data();
978 for (size_t jj = 0; jj < mSettings.SpectrumSize(); ++jj) {
979 *pSum++ += *pPower++;
980 }
981 }
982
983#ifdef OLD_METHOD_AVAILABLE
984 // The noise threshold for each frequency is the maximum
985 // level achieved at that frequency for a minimum of
986 // mMinSignalBlocks blocks in a row - the max of a min.
987
988 auto finish = mHistoryLen;
989
990 {
991 // old statistics
992 auto pPower = NthWindow(0).mSpectrums.data();
993 auto pThreshold = mStatistics.mNoiseThreshold.data();
994 for (size_t jj = 0; jj < mSpectrumSize; ++jj) {
995 float min = *pPower++;
996 for (unsigned ii = 1; ii < finish; ++ii)
997 min = std::min(min, NthWindow(ii).mSpectrums[jj]);
998 *pThreshold = std::max(*pThreshold, min);
999 ++pThreshold;
1000 }
1001 }
1002#endif
1003}
1004
1005// Return true iff the given band of the "center" window looks like noise.
1006// Examine the band in a few neighboring windows to decide.
1007inline
1009 MyTransformer &transformer, unsigned nWindows, int band)
1010{
1011 switch (mMethod) {
1012#ifdef OLD_METHOD_AVAILABLE
1013 case DM_OLD_METHOD:
1014 {
1015 float min = NthWindow(0).mSpectrums[band];
1016 for (unsigned ii = 1; ii < nWindows; ++ii)
1017 min = std::min(min, NthWindow(ii).mSpectrums[band]);
1018 return
1019 min <= mOldSensitivityFactor * mStatistics.mNoiseThreshold[band];
1020 }
1021#endif
1022 // New methods suppose an exponential distribution of power values
1023 // in the noise; NEW sensitivity (which is nonnegative) is meant to be
1024 // the negative of a log of probability (so the log is nonpositive)
1025 // that noise strays above the threshold. Call that probability
1026 // 1 - F. The quantile function of an exponential distribution is
1027 // - log (1 - F) * mean. Thus simply multiply mean by sensitivity
1028 // to get the threshold.
1029 case DM_MEDIAN:
1030 // This method examines the window and all other windows
1031 // whose centers lie on or between its boundaries, and takes a median, to
1032 // avoid being fooled by up and down excursions into
1033 // either the mistake of classifying noise as not noise
1034 // (leaving a musical noise chime), or the opposite
1035 // (distorting the signal with a drop out).
1036 if (nWindows <= 3)
1037 // No different from second greatest.
1038 goto secondGreatest;
1039 else if (nWindows <= 5)
1040 {
1041 float greatest = 0.0, second = 0.0, third = 0.0;
1042 for (unsigned ii = 0; ii < nWindows; ++ii) {
1043 const float power = transformer.NthWindow(ii).mSpectrums[band];
1044 if (power >= greatest)
1045 third = second, second = greatest, greatest = power;
1046 else if (power >= second)
1047 third = second, second = power;
1048 else if (power >= third)
1049 third = power;
1050 }
1051 return third <= mNewSensitivity * mStatistics.mMeans[band];
1052 }
1053 else {
1054 // not implemented
1055 wxASSERT(false);
1056 return true;
1057 }
1058 secondGreatest:
1059 case DM_SECOND_GREATEST:
1060 {
1061 // This method just throws out the high outlier. It
1062 // should be less prone to distortions and more prone to
1063 // chimes.
1064 float greatest = 0.0, second = 0.0;
1065 for (unsigned ii = 0; ii < nWindows; ++ii) {
1066 const float power = transformer.NthWindow(ii).mSpectrums[band];
1067 if (power >= greatest)
1068 second = greatest, greatest = power;
1069 else if (power >= second)
1070 second = power;
1071 }
1072 return second <= mNewSensitivity * mStatistics.mMeans[band];
1073 }
1074 default:
1075 wxASSERT(false);
1076 return true;
1077 }
1078}
1079
1081{
1082 auto historyLen = transformer.CurrentQueueSize();
1083 auto nWindows = std::min<unsigned>(mNWindowsToExamine, historyLen);
1084
1085 const auto spectrumSize = mSettings.SpectrumSize();
1086
1087 if (mNoiseReductionChoice != NRC_ISOLATE_NOISE)
1088 {
1089 auto &record = transformer.NthWindow(0);
1090 // Default all gains to the reduction factor,
1091 // until we decide to raise some of them later
1092 float *pGain = &record.mGains[0];
1093 std::fill(pGain, pGain + spectrumSize, mNoiseAttenFactor);
1094 }
1095
1096 // Raise the gain for elements in the center of the sliding history
1097 // or, if isolating noise, zero out the non-noise
1098 if (nWindows > mCenter)
1099 {
1100 auto pGain = transformer.NthWindow(mCenter).mGains.data();
1101 if (mNoiseReductionChoice == NRC_ISOLATE_NOISE) {
1102 // All above or below the selected frequency range is non-noise
1103 std::fill(pGain, pGain + mBinLow, 0.0f);
1104 std::fill(pGain + mBinHigh, pGain + spectrumSize, 0.0f);
1105 pGain += mBinLow;
1106 for (size_t jj = mBinLow; jj < mBinHigh; ++jj) {
1107 const bool isNoise = Classify(transformer, nWindows, jj);
1108 *pGain++ = isNoise ? 1.0 : 0.0;
1109 }
1110 }
1111 else {
1112 // All above or below the selected frequency range is non-noise
1113 std::fill(pGain, pGain + mBinLow, 1.0f);
1114 std::fill(pGain + mBinHigh, pGain + spectrumSize, 1.0f);
1115 pGain += mBinLow;
1116 for (size_t jj = mBinLow; jj < mBinHigh; ++jj) {
1117 const bool isNoise = Classify(transformer, nWindows, jj);
1118 if (!isNoise)
1119 *pGain = 1.0;
1120 ++pGain;
1121 }
1122 }
1123 }
1124
1125 if (mNoiseReductionChoice != NRC_ISOLATE_NOISE)
1126 {
1127 // In each direction, define an exponential decay of gain from the
1128 // center; make actual gains the maximum of mNoiseAttenFactor, and
1129 // the decay curve, and their prior values.
1130
1131 // First, the attack, which goes backward in time, which is,
1132 // toward higher indices in the queue.
1133 for (size_t jj = 0; jj < spectrumSize; ++jj) {
1134 for (unsigned ii = mCenter + 1; ii < historyLen; ++ii) {
1135 const float minimum =
1136 std::max(mNoiseAttenFactor,
1137 transformer.NthWindow(ii - 1).mGains[jj] * mOneBlockAttack);
1138 float &gain = transformer.NthWindow(ii).mGains[jj];
1139 if (gain < minimum)
1140 gain = minimum;
1141 else
1142 // We can stop now, our attack curve is intersecting
1143 // the release curve of some window previously processed.
1144 break;
1145 }
1146 }
1147
1148 // Now, release. We need only look one window ahead. This part will
1149 // be visited again when we examine the next window, and
1150 // carry the decay further.
1151 {
1152 auto pNextGain = transformer.NthWindow(mCenter - 1).mGains.data();
1153 auto pThisGain = transformer.NthWindow(mCenter).mGains.data();
1154 for (auto nn = mSettings.SpectrumSize(); nn--;) {
1155 *pNextGain =
1156 std::max(*pNextGain,
1157 std::max(mNoiseAttenFactor,
1158 *pThisGain++ * mOneBlockRelease));
1159 ++pNextGain;
1160 }
1161 }
1162 }
1163
1164
1165 if (transformer.QueueIsFull()) {
1166 auto &record = transformer.NthWindow(historyLen - 1); // end of the queue
1167 const auto last = mSettings.SpectrumSize() - 1;
1168
1169 if (mNoiseReductionChoice != NRC_ISOLATE_NOISE)
1170 // Apply frequency smoothing to output gain
1171 // Gains are not less than mNoiseAttenFactor
1172 ApplyFreqSmoothing(record.mGains);
1173
1174 // Apply gain to FFT
1175 {
1176 const float *pGain = &record.mGains[1];
1177 float *pReal = &record.mRealFFTs[1];
1178 float *pImag = &record.mImagFFTs[1];
1179 auto nn = mSettings.SpectrumSize() - 2;
1180 if (mNoiseReductionChoice == NRC_LEAVE_RESIDUE) {
1181 for (; nn--;) {
1182 // Subtract the gain we would otherwise apply from 1, and
1183 // negate that to flip the phase.
1184 const double gain = *pGain++ - 1.0;
1185 *pReal++ *= gain;
1186 *pImag++ *= gain;
1187 }
1188 record.mRealFFTs[0] *= (record.mGains[0] - 1.0);
1189 // The Fs/2 component is stored as the imaginary part of the DC component
1190 record.mImagFFTs[0] *= (record.mGains[last] - 1.0);
1191 }
1192 else {
1193 for (; nn--;) {
1194 const double gain = *pGain++;
1195 *pReal++ *= gain;
1196 *pImag++ *= gain;
1197 }
1198 record.mRealFFTs[0] *= record.mGains[0];
1199 // The Fs/2 component is stored as the imaginary part of the DC component
1200 record.mImagFFTs[0] *= record.mGains[last];
1201 }
1202 }
1203 }
1204}
1205
1207{
1208 if (mWorker.mDoProfile)
1209 mWorker.FinishTrackStatistics();
1211}
1212
1213//----------------------------------------------------------------------------
1214// EffectNoiseReduction::Dialog
1215//----------------------------------------------------------------------------
1216
1217enum {
1220#ifdef ISOLATE_CHOICE
1221 ID_RADIOBUTTON_KEEPNOISE,
1222#endif
1223#ifdef RESIDUE_CHOICE
1225#endif
1226
1227#ifdef ADVANCED_SETTINGS
1228 ID_CHOICE_METHOD,
1229#endif
1230
1231 // Slider/text pairs
1234
1237
1238#ifdef ATTACK_AND_RELEASE
1239 ID_ATTACK_TIME_SLIDER,
1240 ID_ATTACK_TIME_TEXT,
1241
1242 ID_RELEASE_TIME_SLIDER,
1243 ID_RELEASE_TIME_TEXT,
1244#endif
1245
1248
1250
1251#ifdef ADVANCED_SETTINGS
1252 ID_OLD_SENSITIVITY_SLIDER = END_OF_BASIC_SLIDERS,
1253 ID_OLD_SENSITIVITY_TEXT,
1254
1255 END_OF_ADVANCED_SLIDERS,
1256 END_OF_SLIDERS = END_OF_ADVANCED_SLIDERS,
1257#else
1259#endif
1260
1262};
1263
1264namespace {
1265
1268
1269 double Value(long sliderSetting) const
1270 {
1271 return
1272 valueMin +
1273 (double(sliderSetting) / sliderMax) * (valueMax - valueMin);
1274 }
1275
1276 long SliderSetting(double value) const
1277 {
1278 return std::clamp<long>(
1279 0.5 + sliderMax * (value - valueMin) / (valueMax - valueMin),
1280 0, sliderMax);
1281 }
1282
1283 wxString Text(double value) const
1284 {
1285 if (formatAsInt)
1286 return wxString::Format(format, (int)(value));
1287 else
1288 return wxString::Format(format, value);
1289 }
1290
1291 void CreateControls(int id, ShuttleGui &S) const
1292 {
1293 wxTextCtrl *const text = S.Id(id + 1)
1294 .Validator<FloatingPointValidator<double>>(
1295 formatAsInt ? 0 : 2,
1296 nullptr,
1297 NumValidatorStyle::DEFAULT,
1298 valueMin, valueMax
1299 )
1300 .AddTextBox(textBoxCaption, wxT(""), 0);
1301
1302 wxSlider *const slider =
1303 S.Id(id)
1304 .Name( sliderName )
1305 .Style(wxSL_HORIZONTAL)
1306 .MinSize( { 150, -1 } )
1307 .AddSlider( {}, 0, sliderMax);
1308 }
1309
1311 double valueMin;
1312 double valueMax;
1314 // (valueMin - valueMax) / sliderMax is the value increment of the slider
1315 const wxChar* format;
1319
1320 ControlInfo(MemberPointer f, double vMin, double vMax, long sMax, const wxChar* fmt, bool fAsInt,
1321 const TranslatableString &caption, const TranslatableString &name)
1322 : field(f), valueMin(vMin), valueMax(vMax), sliderMax(sMax), format(fmt), formatAsInt(fAsInt)
1323 , textBoxCaption(caption), sliderName(name)
1324 {
1325 }
1326};
1327
1329 static const ControlInfo table[] = {
1331 0.0, 48.0, 48, wxT("%d"), true,
1332 XXO("&Noise reduction (dB):"), XO("Noise reduction")),
1334 0.01, 24.0, 48, wxT("%.2f"), false,
1335 XXO("&Sensitivity:"), XO("Sensitivity")),
1336#ifdef ATTACK_AND_RELEASE
1338 0, 1.0, 100, wxT("%.2f"), false,
1339 XXO("Attac&k time (secs):"), XO("Attack time")),
1341 0, 1.0, 100, wxT("%.2f"), false,
1342 XXO("R&elease time (secs):"), XO("Release time")),
1343#endif
1345 0, 12, 12, wxT("%d"), true,
1346 XXO("&Frequency smoothing (bands):"), XO("Frequency smoothing")),
1347
1348#ifdef ADVANCED_SETTINGS
1350 -20.0, 20.0, 4000, wxT("%.2f"), false,
1351 XXO("Sensiti&vity (dB):"), XO("Old Sensitivity")),
1352 // add here
1353#endif
1354 };
1355
1356return table;
1357}
1358
1359} // namespace
1360
1361
1368
1370#ifdef ISOLATE_CHOICE
1371 EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPNOISE, EffectNoiseReduction::Dialog::OnNoiseReductionChoice)
1372#endif
1373#ifdef RESIDUE_CHOICE
1375#endif
1376
1377#ifdef ADVANCED_SETTINGS
1378 EVT_CHOICE(ID_CHOICE_METHOD, EffectNoiseReduction::Dialog::OnMethodChoice)
1379#endif
1380
1383
1386
1389
1390#ifdef ATTACK_AND_RELEASE
1391 EVT_SLIDER(ID_ATTACK_TIME_SLIDER, EffectNoiseReduction::Dialog::OnSlider)
1392 EVT_TEXT(ID_ATTACK_TIME_TEXT, EffectNoiseReduction::Dialog::OnText)
1393
1394 EVT_SLIDER(ID_RELEASE_TIME_SLIDER, EffectNoiseReduction::Dialog::OnSlider)
1395 EVT_TEXT(ID_RELEASE_TIME_TEXT, EffectNoiseReduction::Dialog::OnText)
1396#endif
1397
1398
1399#ifdef ADVANCED_SETTINGS
1400 EVT_SLIDER(ID_OLD_SENSITIVITY_SLIDER, EffectNoiseReduction::Dialog::OnSlider)
1401 EVT_TEXT(ID_OLD_SENSITIVITY_TEXT, EffectNoiseReduction::Dialog::OnText)
1402#endif
1404
1406 EffectSettingsAccess &access,
1408 wxWindow *parent, bool bHasProfile, bool bAllowTwiddleSettings)
1409 : EffectDialog( parent, XO("Noise Reduction"), EffectTypeProcess,wxDEFAULT_DIALOG_STYLE, eHelpButton )
1410 , m_pEffect(effect)
1411 , mAccess{access}
1412 , m_pSettings(settings) // point to
1413 , mTempSettings(*settings) // copy
1414 , mbHasProfile(bHasProfile)
1415 , mbAllowTwiddleSettings(bAllowTwiddleSettings)
1416 // NULL out the control members until the controls are created.
1417 , mKeepSignal(NULL)
1418#ifdef ISOLATE_CHOICE
1419 , mKeepNoise(NULL)
1420#endif
1421#ifdef RESIDUE_CHOICE
1422 , mResidue(NULL)
1423#endif
1424{
1426
1427 wxButton *const pButtonPreview =
1428 (wxButton *)wxWindow::FindWindowById(ID_EFFECT_PREVIEW, this);
1429 wxButton *const pButtonReduceNoise =
1430 (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
1431
1432 if (mbHasProfile || mbAllowTwiddleSettings) {
1433 pButtonPreview->Enable(!mbAllowTwiddleSettings);
1434 pButtonReduceNoise->SetFocus();
1435 }
1436 else {
1437 pButtonPreview->Enable(false);
1438 pButtonReduceNoise->Enable(false);
1439 }
1440}
1441
1443{
1444 // If Isolate is chosen, disable controls that define
1445 // "what to do with noise" rather than "what is noise."
1446 // Else, enable them.
1447 // This does NOT include sensitivity, NEW or old, nor
1448 // the choice of window functions, size, or step.
1449 // The method choice is not included, because it affects
1450 // which sensitivity slider is operative, and that is part
1451 // of what defines noise.
1452
1453 static const int toDisable[] = {
1456
1459
1460#ifdef ATTACK_AND_RELEASE
1461 ID_ATTACK_TIME_SLIDER,
1462 ID_ATTACK_TIME_TEXT,
1463
1464 ID_RELEASE_TIME_SLIDER,
1465 ID_RELEASE_TIME_TEXT,
1466#endif
1467 };
1468 static const auto nToDisable = sizeof(toDisable) / sizeof(toDisable[0]);
1469
1470 bool bIsolating =
1471#ifdef ISOLATE_CHOICE
1472 mKeepNoise->GetValue();
1473#else
1474 false;
1475#endif
1476 for (auto ii = nToDisable; ii--;)
1477 wxWindow::FindWindowById(toDisable[ii], this)->Enable(!bIsolating);
1478}
1479
1480#ifdef ADVANCED_SETTINGS
1481void EffectNoiseReduction::Dialog::EnableDisableSensitivityControls()
1482{
1483 wxChoice *const pChoice =
1484 static_cast<wxChoice*>(wxWindow::FindWindowById(ID_CHOICE_METHOD, this));
1485 const bool bOldMethod =
1486 pChoice->GetSelection() == DM_OLD_METHOD;
1487 wxWindow::FindWindowById(ID_OLD_SENSITIVITY_SLIDER, this)->Enable(bOldMethod);
1488 wxWindow::FindWindowById(ID_OLD_SENSITIVITY_TEXT, this)->Enable(bOldMethod);
1489 wxWindow::FindWindowById(ID_NEW_SENSITIVITY_SLIDER, this)->Enable(!bOldMethod);
1490 wxWindow::FindWindowById(ID_NEW_SENSITIVITY_TEXT, this)->Enable(!bOldMethod);
1491}
1492#endif
1493
1494void EffectNoiseReduction::Dialog::OnGetProfile(wxCommandEvent & WXUNUSED(event))
1495{
1496 // Project has not be changed so skip pushing state
1498
1500 return;
1501
1502 // Return code distinguishes this first step from the actual effect
1503 EndModal(1);
1504}
1505
1506// This handles the whole radio group
1507void EffectNoiseReduction::Dialog::OnNoiseReductionChoice( wxCommandEvent & WXUNUSED(event))
1508{
1509 if (mKeepSignal->GetValue())
1510 mTempSettings.mNoiseReductionChoice = NRC_REDUCE_NOISE;
1511#ifdef ISOLATE_CHOICE
1512 else if (mKeepNoise->GetValue())
1513 mTempSettings.mNoiseReductionChoice = NRC_ISOLATE_NOISE;
1514#endif
1515#ifdef RESIDUE_CHOICE
1516 else
1517 mTempSettings.mNoiseReductionChoice = NRC_LEAVE_RESIDUE;
1518#endif
1519 DisableControlsIfIsolating();
1520}
1521
1522#ifdef ADVANCED_SETTINGS
1523void EffectNoiseReduction::Dialog::OnMethodChoice(wxCommandEvent &)
1524{
1525 EnableDisableSensitivityControls();
1526}
1527#endif
1528
1529void EffectNoiseReduction::Dialog::OnPreview(wxCommandEvent & WXUNUSED(event))
1530{
1532 return;
1533
1534 // Save & restore parameters around Preview, because we didn't do OK.
1535 auto cleanup = valueRestorer( *m_pSettings );
1536 *m_pSettings = mTempSettings;
1537 m_pSettings->mDoProfile = false;
1538
1539 EffectPreview(*m_pEffect, mAccess,
1540 // Don't need any UI updates for preview
1541 {},
1542 false);
1543}
1544
1545void EffectNoiseReduction::Dialog::OnReduceNoise( wxCommandEvent & WXUNUSED(event))
1546{
1548 return;
1549
1550 EndModal(2);
1551}
1552
1553void EffectNoiseReduction::Dialog::OnCancel(wxCommandEvent & WXUNUSED(event))
1554{
1555 EndModal(0);
1556}
1557
1558void EffectNoiseReduction::Dialog::OnHelp(wxCommandEvent & WXUNUSED(event))
1559{
1560 HelpSystem::ShowHelp(this, "Noise_Reduction", true);
1561}
1562
1564{
1565 S.StartStatic(XO("Step 1"));
1566 {
1567 S.AddVariableText(XO(
1568"Select a few seconds of just noise so Audacity knows what to filter out,\nthen click Get Noise Profile:"));
1569 //m_pButton_GetProfile =
1570 S.Id(ID_BUTTON_GETPROFILE).AddButton(XXO("&Get Noise Profile"));
1571 }
1572 S.EndStatic();
1573
1574 S.StartStatic(XO("Step 2"));
1575 {
1576 S.AddVariableText(XO(
1577"Select all of the audio you want filtered, choose how much noise you want\nfiltered out, and then click 'OK' to reduce noise.\n"));
1578
1579 S.StartMultiColumn(3, wxEXPAND);
1580 S.SetStretchyCol(2);
1581 {
1582 for (int id = FIRST_SLIDER; id < END_OF_BASIC_SLIDERS; id += 2) {
1583 const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2];
1584 info.CreateControls(id, S);
1585 }
1586 }
1587 S.EndMultiColumn();
1588
1589 S.StartMultiColumn(
1590 2
1591#ifdef RESIDUE_CHOICE
1592 +1
1593#endif
1594#ifdef ISOLATE_CHOICE
1595 +1
1596#endif
1597 ,
1598 wxALIGN_CENTER_HORIZONTAL);
1599 {
1600 S.AddPrompt(XXO("Noise:"));
1601 mKeepSignal = S.Id(ID_RADIOBUTTON_KEEPSIGNAL)
1602 /* i18n-hint: Translate differently from "Residue" ! */
1603 .AddRadioButton(XXO("Re&duce"));
1604#ifdef ISOLATE_CHOICE
1605 mKeepNoise = S.Id(ID_RADIOBUTTON_KEEPNOISE)
1606 .AddRadioButtonToGroup(XXO("&Isolate"));
1607#endif
1608#ifdef RESIDUE_CHOICE
1609 mResidue = S.Id(ID_RADIOBUTTON_RESIDUE)
1610 /* i18n-hint: Means the difference between effect and original sound. Translate differently from "Reduce" ! */
1611 .AddRadioButtonToGroup(XXO("Resid&ue"));
1612#endif
1613 }
1614 S.EndMultiColumn();
1615 }
1616 S.EndStatic();
1617
1618
1619#ifdef ADVANCED_SETTINGS
1620 S.StartStatic(XO("Advanced Settings"));
1621 {
1622 S.StartMultiColumn(2);
1623 {
1624 S.TieChoice(XXO("&Window types:"),
1625 mTempSettings.mWindowTypes,
1626 []{
1627 TranslatableStrings windowTypeChoices;
1628 for (size_t ii = 0; ii < WT_N_WINDOW_TYPES; ++ii)
1629 windowTypeChoices.push_back(windowTypesInfo[ii].name);
1630 return windowTypeChoices;
1631 }()
1632 );
1633
1634 S.TieChoice(XXO("Window si&ze:"),
1635 mTempSettings.mWindowSizeChoice,
1636 {
1637 XO("8") ,
1638 XO("16") ,
1639 XO("32") ,
1640 XO("64") ,
1641 XO("128") ,
1642 XO("256") ,
1643 XO("512") ,
1644 XO("1024") ,
1645 XO("2048 (default)") ,
1646 XO("4096") ,
1647 XO("8192") ,
1648 XO("16384") ,
1649 }
1650 );
1651
1652 S.TieChoice(XXO("S&teps per window:"),
1653 mTempSettings.mStepsPerWindowChoice,
1654 {
1655 XO("2") ,
1656 XO("4 (default)") ,
1657 XO("8") ,
1658 XO("16") ,
1659 XO("32") ,
1660 XO("64") ,
1661 }
1662 );
1663
1664 S.Id(ID_CHOICE_METHOD)
1665 .TieChoice(XXO("Discrimination &method:"),
1666 mTempSettings.mMethod,
1667 []{
1668 TranslatableStrings methodChoices;
1669 auto nn = DM_N_METHODS;
1670#ifndef OLD_METHOD_AVAILABLE
1671 --nn;
1672#endif
1673 for (auto ii = 0; ii < nn; ++ii)
1674 methodChoices.push_back(discriminationMethodInfo[ii].name);
1675 return methodChoices;
1676 }());
1677 }
1678 S.EndMultiColumn();
1679
1680 S.StartMultiColumn(3, wxEXPAND);
1681 S.SetStretchyCol(2);
1682 {
1683 for (int id = END_OF_BASIC_SLIDERS; id < END_OF_ADVANCED_SLIDERS; id += 2) {
1684 const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2];
1685 info.CreateControls(id, S);
1686 }
1687 }
1688 S.EndMultiColumn();
1689 }
1690 S.EndStatic();
1691#endif
1692}
1693
1695{
1696 // Do the choice controls:
1698 return false;
1699
1700 for (int id = FIRST_SLIDER; id < END_OF_SLIDERS; id += 2) {
1701 wxSlider* slider =
1702 static_cast<wxSlider*>(wxWindow::FindWindowById(id, this));
1703 wxTextCtrl* text =
1704 static_cast<wxTextCtrl*>(wxWindow::FindWindowById(id + 1, this));
1705 const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2];
1706 const double field = mTempSettings.*(info.field);
1707 text->SetValue(info.Text(field));
1708 slider->SetValue(info.SliderSetting(field));
1709 }
1710
1711 mKeepSignal->SetValue(mTempSettings.mNoiseReductionChoice == NRC_REDUCE_NOISE);
1712#ifdef ISOLATE_CHOICE
1713 mKeepNoise->SetValue(mTempSettings.mNoiseReductionChoice == NRC_ISOLATE_NOISE);
1714#endif
1715#ifdef RESIDUE_CHOICE
1716 mResidue->SetValue(mTempSettings.mNoiseReductionChoice == NRC_LEAVE_RESIDUE);
1717#endif
1718
1719 // Set the enabled states of controls
1720 DisableControlsIfIsolating();
1721#ifdef ADVANCED_SETTINGS
1722 EnableDisableSensitivityControls();
1723#endif
1724
1725 return true;
1726}
1727
1729{
1730 if( !wxWindow::Validate() )
1731 return false;
1732 // Do the choice controls:
1734 return false;
1735
1736 wxCommandEvent dummy;
1737 OnNoiseReductionChoice(dummy);
1738
1739 return mTempSettings.Validate(m_pEffect);
1740}
1741
1742void EffectNoiseReduction::Dialog::OnText(wxCommandEvent &event)
1743{
1744 int id = event.GetId();
1745 int idx = (id - FIRST_SLIDER - 1) / 2;
1746 const ControlInfo &info = controlInfo()[idx];
1747 wxTextCtrl* text =
1748 static_cast<wxTextCtrl*>(wxWindow::FindWindowById(id, this));
1749 wxSlider* slider =
1750 static_cast<wxSlider*>(wxWindow::FindWindowById(id - 1, this));
1751 double &field = mTempSettings.*(info.field);
1752
1753 text->GetValue().ToDouble(&field);
1754 slider->SetValue(info.SliderSetting(field));
1755}
1756
1758{
1759 int id = event.GetId();
1760 int idx = (id - FIRST_SLIDER) / 2;
1761 const ControlInfo &info = controlInfo()[idx];
1762 wxSlider* slider =
1763 static_cast<wxSlider*>(wxWindow::FindWindowById(id, this));
1764 wxTextCtrl* text =
1765 static_cast<wxTextCtrl*>(wxWindow::FindWindowById(id + 1, this));
1766 double &field = mTempSettings.*(info.field);
1767
1768 field = info.Value(slider->GetValue());
1769 text->SetValue(info.Text(field));
1770}
wxT("CloseDown"))
END_EVENT_TABLE()
int min(int a, int b)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
const TranslatableString name
Definition: Distortion.cpp:76
#define ID_EFFECT_PREVIEW
Definition: Effect.h:187
EffectType
@ EffectTypeProcess
void EffectPreview(EffectBase &effect, EffectSettingsAccess &access, std::function< void()> updateUI, bool dryOnly)
Calculate temporary tracks of limited length with effect applied and play.
std::function< DialogFactoryResults(wxWindow &parent, EffectBase &, EffectUIServices &, EffectSettingsAccess &) > EffectDialogFactory
Type of function that creates a dialog for an effect.
eWindowFunctions
Definition: FFT.h:110
@ eWinFuncRectangular
Definition: FFT.h:111
@ eWinFuncHamming
Definition: FFT.h:113
@ eWinFuncBlackman
Definition: FFT.h:115
@ eWinFuncHann
Definition: FFT.h:114
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define field(n, t)
Definition: ImportAUP.cpp:165
static ProjectFileIORegistry::AttributeWriterEntry entry
ValueRestorer< T > valueRestorer(T &var)
inline functions provide convenient parameter type deduction
Definition: MemoryX.h:252
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:337
#define RESIDUE_CHOICE
std::vector< float > FloatVector
@ ID_GAIN_TEXT
@ ID_BUTTON_GETPROFILE
@ ID_GAIN_SLIDER
@ ID_NEW_SENSITIVITY_SLIDER
@ ID_FREQ_SLIDER
@ END_OF_SLIDERS
@ END_OF_BASIC_SLIDERS
@ ID_RADIOBUTTON_RESIDUE
@ ID_NEW_SENSITIVITY_TEXT
@ ID_RADIOBUTTON_KEEPSIGNAL
@ FIRST_SLIDER
@ ID_FREQ_TEXT
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
@ eHelpButton
Definition: ShuttleGui.h:613
const auto tracks
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:69
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
Base class for many of the effects in Audacity.
Definition: EffectBase.h:28
double mT1
Definition: EffectBase.h:114
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:107
double mF0
Definition: EffectBase.h:94
double mF1
Definition: EffectBase.h:95
double mT0
Definition: EffectBase.h:113
bool TransferDataToWindow() override
Definition: EffectUI.cpp:1397
bool TransferDataFromWindow() override
Definition: EffectUI.cpp:1405
bool IsBatchProcessing() const override
Definition: Effect.cpp:295
Performs effect computation.
void SetSkipStateFlag(bool flag)
static EffectManager & Get()
Dialog used with EffectNoiseReduction.
const Settings & GetTempSettings() const
void OnNoiseReductionChoice(wxCommandEvent &event)
void OnSlider(wxCommandEvent &event)
void OnHelp(wxCommandEvent &event)
EffectNoiseReduction::Settings mTempSettings
EffectSettingsAccess & mAccess
This dialog is modal, so mAccess will live long enough for it.
void PopulateOrExchange(ShuttleGui &S) override
void OnText(wxCommandEvent &event)
Dialog(EffectNoiseReduction *effect, EffectSettingsAccess &access, Settings *settings, wxWindow *parent, bool bHasProfile, bool bAllowTwiddleSettings)
void OnReduceNoise(wxCommandEvent &event)
void OnPreview(wxCommandEvent &event) override
EffectNoiseReduction * m_pEffect
void OnGetProfile(wxCommandEvent &event)
void OnCancel(wxCommandEvent &event)
EffectNoiseReduction::Settings * m_pSettings
bool TransferDataFromWindow() override
int PromptUser(EffectNoiseReduction *effect, EffectSettingsAccess &access, wxWindow &parent, bool bHasProfile, bool bAllowTwiddleSettings)
bool Validate(EffectNoiseReduction *effect) const
Statistics(size_t spectrumSize, double rate, int windowTypes)
EffectNoiseReduction::Settings Settings
void ReduceNoise(MyTransformer &transformer)
bool Process(eWindowFunctions inWindowType, eWindowFunctions outWindowType, TrackList &tracks, double mT0, double mT1)
static bool Processor(SpectrumTransformer &transformer)
void ApplyFreqSmoothing(FloatVector &gains)
EffectNoiseReduction & mEffect
EffectNoiseReduction::Statistics Statistics
void GatherStatistics(MyTransformer &transformer)
bool Classify(MyTransformer &transformer, unsigned nWindows, int band)
Worker(EffectNoiseReduction &effect, const Settings &settings, Statistics &statistics)
A two-pass effect to reduce background noise.
static const ComponentInterfaceSymbol Symbol
TranslatableString GetDescription() const override
ComponentInterfaceSymbol GetSymbol() const override
std::unique_ptr< Settings > mSettings
bool Process(EffectInstance &instance, EffectSettings &settings) override
EffectType GetType() const override
Type determines how it behaves.
int ShowHostInterface(EffectBase &plugin, wxWindow &parent, const EffectDialogFactory &factory, std::shared_ptr< EffectInstance > &pInstance, EffectSettingsAccess &access, bool forceModal=false) override
std::unique_ptr< Statistics > mStatistics
Use this object to copy the input tracks to tentative outputTracks.
static int DoMessageBox(const EffectPlugin &plugin, const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={})
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:231
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
A class that transforms a portion of a wave track (preserving duration) by applying Fourier transform...
std::vector< float > FloatVector
size_t CurrentQueueSize() const
How many windows in the queue have been filled?
std::shared_ptr< EffectInstance > MakeInstance() const override
Make an object maintaining short-term state of an Effect.
virtual bool TransferDataFromWindow(EffectSettings &settings)
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
Subclass of SpectrumTransformer that rewrites a track.
bool DoFinish() override
Called after the last call to ProcessWindow().
static bool PostProcess(WaveTrack &outputTrack, sampleCount len)
Final flush and trimming of tail samples.
bool DoStart() override
Called before any calls to ProcessWindow.
Holds a msgid for the translation catalog; may also bind format arguments.
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
virtual bool Flush() noexcept=0
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
void writePrefs(const StructureType *structure, const wxString &prefix, const PrefsTableEntry< StructureType, FieldType > *fields, size_t numFields)
void readPrefs(StructureType *structure, const wxString &prefix, const PrefsTableEntry< StructureType, FieldType > *fields, size_t numFields)
BuiltinEffectsModule::Registration< EffectNoiseReduction > reg
const struct anonymous_namespace{NoiseReduction.cpp}::DiscriminationMethodInfo discriminationMethodInfo[DM_N_METHODS]
const struct anonymous_namespace{NoiseReduction.cpp}::WindowTypesInfo windowTypesInfo[WT_N_WINDOW_TYPES]
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept
Definition: fast_float.h:1454
STL namespace.
Externalized state of a plug-in.
MyWindow(size_t windowSize)
bool DoFinish() override
Called after the last call to ProcessWindow().
std::unique_ptr< Window > NewWindow(size_t windowSize) override
Allocates a window to place in the queue.
bool DoStart() override
Called before any calls to ProcessWindow.
EffectNoiseReduction::Worker & mWorker
MyTransformer(EffectNoiseReduction::Worker &worker, WaveChannel *pOutputTrack, bool needsOutput, eWindowFunctions inWindowType, eWindowFunctions outWindowType, size_t windowSize, unsigned stepsPerWindow, bool leadingPadding, bool trailingPadding)
MyWindow & NthWindow(int nn)
doubleEffectNoiseReduction::Settings::* MemberPointer
ControlInfo(MemberPointer f, double vMin, double vMax, long sMax, const wxChar *fmt, bool fAsInt, const TranslatableString &caption, const TranslatableString &name)
FieldType defaultValue
MemberPointer field
const wxChar * name
FieldTypeStructureType::* MemberPointer