Audacity 3.2.0
NoiseReductionBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NoiseReductionBase.cpp
6
7 Dominic Mazzoni
8
9 detailed rewriting by
10 Paul Licameli
11
12*******************************************************************//********************************************************************/
37#include "NoiseReductionBase.h"
38#include "BasicUI.h"
39#include "EffectOutputTracks.h"
40#include "FFT.h"
42#include "WaveTrack.h"
43#include <algorithm>
44#include <cmath>
45
46// SPECTRAL_SELECTION not to affect this effect for now, as there might be no
47// indication that it does. [Discussed and agreed for v2.1 by Steve, Paul,
48// Bill].
49#undef SPECTRAL_EDIT_NOISE_REDUCTION
50
51typedef std::vector<float> FloatVector;
52
53// Define both of these to make the radio button three-way
54#define RESIDUE_CHOICE
55//#define ISOLATE_CHOICE
56
57// Define for Attack and release controls.
58// #define ATTACK_AND_RELEASE
59
60// Define to expose other advanced, experimental dialog controls
61//#define ADVANCED_SETTINGS
62
63// Define to make the old statistical methods an available choice
64//#define OLD_METHOD_AVAILABLE
65
66namespace
67{
68
70{
74
77};
78
80{
83 // Experimental only, don't need translations
84 { XO("Median") },
85 { XO("Second greatest") },
86 { XO("Old") },
87};
88
89// magic number used only in the old statistics
90// and the old discrimination
91const float minSignalTime = 0.05f;
92
93enum WindowTypes : unsigned
94{
95 WT_RECTANGULAR_HANN = 0, // 2.0.6 behavior, requires 1/2 step
96 WT_HANN_RECTANGULAR, // requires 1/2 step
97 WT_HANN_HANN, // requires 1/4 step
98 WT_BLACKMAN_HANN, // requires 1/4 step
99 WT_HAMMING_RECTANGULAR, // requires 1/2 step
100 WT_HAMMING_HANN, // requires 1/4 step
101 // WT_HAMMING_INV_HAMMING, // requires 1/2 step
102
106
107const struct WindowTypesInfo
108{
110 unsigned minSteps;
112
113 // Experimental only, don't need translations
114 { Verbatim("none, Hann (2.0.6 behavior)"), 2 },
115 /* i18n-hint: Hann is a proper name */
116 { Verbatim("Hann, none"), 2 },
117 /* i18n-hint: Hann is a proper name */
118 { Verbatim("Hann, Hann (default)"), 4 },
119 /* i18n-hint: Hann and Blackman are proper names */
120 { Verbatim("Blackman, Hann"), 4 },
121 /* i18n-hint: Hamming is a proper name */
122 { Verbatim("Hamming, none"), 2 },
123 /* i18n-hint: Hamming and Hann area proper names */
124 { Verbatim("Hamming, Hann"), 4 },
125 /* i18n-hint: Hamming is a proper name */
126 // { XO("Hamming, Reciprocal Hamming"), 2, }, // output window is special
128
129enum
130{
131 DEFAULT_WINDOW_SIZE_CHOICE = 8, // corresponds to 2048
133 1 // corresponds to 4, minimum for WT_HANN_HANN
135} // namespace
136
137//----------------------------------------------------------------------------
138// NoiseReductionBase::Statistics
139//----------------------------------------------------------------------------
140
142{
143public:
144 Statistics(size_t spectrumSize, double rate, int windowTypes)
145 : mRate { rate }
146 , mWindowSize { (spectrumSize - 1) * 2 }
147 , mWindowTypes { windowTypes }
148 , mTotalWindows { 0 }
149 , mTrackWindows { 0 }
150 , mSums(spectrumSize)
151 , mMeans(spectrumSize)
152#ifdef OLD_METHOD_AVAILABLE
153 , mNoiseThreshold(spectrumSize)
154#endif
155 {
156 }
157
158 // Noise profile statistics follow
159
160 double mRate; // Rate of profile track(s) -- processed tracks must match
163
168
169#ifdef OLD_METHOD_AVAILABLE
170 // Old statistics:
171 FloatVector mNoiseThreshold;
172#endif
173};
174
175//----------------------------------------------------------------------------
176// NoiseReductionBase::Settings
177//----------------------------------------------------------------------------
178
180 : mDoProfile { true }
181{
182 PrefsIO(true);
183}
184
186{
188 NoiseReductionBase::Worker& worker, WaveChannel* pOutputTrack,
189 bool needsOutput, eWindowFunctions inWindowType,
190 eWindowFunctions outWindowType, size_t windowSize,
191 unsigned stepsPerWindow, bool leadingPadding, bool trailingPadding)
192 : TrackSpectrumTransformer { pOutputTrack, needsOutput,
193 inWindowType, outWindowType,
194 windowSize, stepsPerWindow,
195 leadingPadding, trailingPadding }
196 , mWorker { worker }
197 {
198 }
199 struct MyWindow : public Window
200 {
201 explicit MyWindow(size_t windowSize)
202 : Window { windowSize }
203 , mSpectrums(windowSize / 2 + 1)
204 , mGains(windowSize / 2 + 1)
205 {
206 }
207 ~MyWindow() override;
208
211 };
212
214 {
215 return static_cast<MyWindow&>(Nth(nn));
216 }
217 std::unique_ptr<Window> NewWindow(size_t windowSize) override;
218 bool DoStart() override;
219 bool DoFinish() override;
220
222};
223
224//----------------------------------------------------------------------------
225// NoiseReductionBase::Worker
226//----------------------------------------------------------------------------
227
228// This object holds information needed only during effect calculation
230{
231public:
234
235 Worker(
236 NoiseReductionBase& effect, const Settings& settings,
237 Statistics& statistics
238#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
239 ,
240 double f0, double f1
241#endif
242 );
243 ~Worker();
244
245 bool Process(
246 eWindowFunctions inWindowType, eWindowFunctions outWindowType,
247 TrackList& tracks, double mT0, double mT1);
248
249 static bool Processor(SpectrumTransformer& transformer);
250
251 void ApplyFreqSmoothing(FloatVector& gains);
252 void GatherStatistics(MyTransformer& transformer);
253 inline bool
254 Classify(MyTransformer& transformer, unsigned nWindows, int band);
255 void ReduceNoise(MyTransformer& transformer);
257
258 const bool mDoProfile;
259
263
265 const size_t mFreqSmoothingBins;
266 // When spectral selection limits the affected band:
267 size_t mBinLow; // inclusive lower bound
268 size_t mBinHigh; // exclusive upper bound
269
271 const int mMethod;
272 const double mNewSensitivity;
273
278
280 unsigned mCenter;
281 unsigned mHistoryLen;
282
283 // Following are for progress indicator only:
287};
288
290 "Noise Reduction") };
291
293 : mSettings(std::make_unique<NoiseReductionBase::Settings>())
294{
295}
296
298{
299}
300
301// ComponentInterface implementation
302
304{
305 return Symbol;
306}
307
309{
310 return XO("Removes background noise such as fans, tape noise, or hums");
311}
312
313// EffectDefinitionInterface implementation
314
316{
317 return EffectTypeProcess;
318}
319
320namespace
321{
322template <typename StructureType, typename FieldType> struct PrefsTableEntry
323{
324 typedef FieldType(StructureType::*MemberPointer);
325
327 const wxChar* name;
328 FieldType defaultValue;
329};
330
331template <typename StructureType, typename FieldType>
333 StructureType* structure, const wxString& prefix,
334 const PrefsTableEntry<StructureType, FieldType>* fields, size_t numFields)
335{
336 for (size_t ii = 0; ii < numFields; ++ii)
337 {
339 gPrefs->Read(
340 prefix + entry.name, &(structure->*(entry.field)), entry.defaultValue);
341 }
342}
343
344template <typename StructureType, typename FieldType>
346 const StructureType* structure, const wxString& prefix,
347 const PrefsTableEntry<StructureType, FieldType>* fields, size_t numFields)
348{
349 for (size_t ii = 0; ii < numFields; ++ii)
350 {
352 gPrefs->Write(prefix + entry.name, structure->*(entry.field));
353 }
354}
355} // namespace
356
358{
359 static const double DEFAULT_OLD_SENSITIVITY = 0.0;
360
361 static const PrefsTableEntry<Settings, double> doubleTable[] = {
362 { &Settings::mNewSensitivity, wxT("Sensitivity"), 6.0 },
363 { &Settings::mNoiseGain, wxT("Gain"), 6.0 },
364 { &Settings::mAttackTime, wxT("AttackTime"), 0.02 },
365 { &Settings::mReleaseTime, wxT("ReleaseTime"), 0.10 },
366 { &Settings::mFreqSmoothingBands, wxT("FreqSmoothing"), 6.0 },
367
368 // Advanced settings
369 { &Settings::mOldSensitivity, wxT("OldSensitivity"),
370 DEFAULT_OLD_SENSITIVITY },
371 };
372 static auto doubleTableSize = sizeof(doubleTable) / sizeof(doubleTable[0]);
373
374 static const PrefsTableEntry<Settings, int> intTable[] = {
375 { &Settings::mNoiseReductionChoice, wxT("ReductionChoice"),
377
378 // Advanced settings
380 { &Settings::mWindowSizeChoice, wxT("WindowSize"),
382 { &Settings::mStepsPerWindowChoice, wxT("StepsPerWindow"),
384 { &Settings::mMethod, wxT("Method"), DM_DEFAULT_METHOD },
385 };
386 static auto intTableSize = sizeof(intTable) / sizeof(intTable[0]);
387
388 static const wxString prefix(wxT("/Effects/NoiseReduction/"));
389
390 if (read)
391 {
392 readPrefs(this, prefix, doubleTable, doubleTableSize);
393 readPrefs(this, prefix, intTable, intTableSize);
394
395 // Ignore preferences for unavailable options.
396#if !(defined(RESIDUE_CHOICE) || defined(ISOLATE_CHOICE))
398#elif !(defined(RESIDUE_CHOICE))
401#elif !(defined(ISOLATE_CHOICE))
404#endif
405
406#ifndef ADVANCED_SETTINGS
407 // Initialize all hidden advanced settings to defaults.
412 mOldSensitivity = DEFAULT_OLD_SENSITIVITY;
413#endif
414
415#ifndef OLD_METHOD_AVAILABLE
416 if (mMethod == DM_OLD_METHOD)
418#endif
419
420 return true;
421 }
422 else
423 {
424 writePrefs(this, prefix, doubleTable, doubleTableSize);
425 writePrefs(this, prefix, intTable, intTableSize);
426 return gPrefs->Flush();
427 }
428}
429
431{
432 using namespace BasicUI;
433 if (StepsPerWindow() < windowTypesInfo[mWindowTypes].minSteps)
434 {
435 ShowMessageBox(XO("Steps per block are too few for the window types."));
436 return false;
437 }
438
439 if (StepsPerWindow() > WindowSize())
440 {
441 ShowMessageBox(XO("Steps per block cannot exceed the window size."));
442 return false;
443 }
444
445 if (mMethod == DM_MEDIAN && StepsPerWindow() > 4)
446 {
448 "Median method is not implemented for more than four steps per window."));
449 return false;
450 }
451
452 return true;
453}
454
455auto MyTransformer::NewWindow(size_t windowSize) -> std::unique_ptr<Window>
456{
457 return std::make_unique<MyWindow>(windowSize);
458}
459
461{
462}
463
465{
466 // This same code will either reduce noise or profile it
467
468 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
469
470 auto track = *(outputs.Get().Selected<const WaveTrack>()).begin();
471 if (!track)
472 return false;
473
474 const auto stepsPerWindow = mSettings->StepsPerWindow();
475 const auto stepSize = mSettings->WindowSize() / stepsPerWindow;
476
477 // Initialize statistics if gathering them, or check for mismatched
478 // (advanced) settings if reducing noise.
479 if (mSettings->mDoProfile)
480 {
481 const auto spectrumSize = mSettings->SpectrumSize();
482 mStatistics = std::make_unique<Statistics>(
483 spectrumSize, track->GetRate(), mSettings->mWindowTypes);
484 }
485 else if (mStatistics->mWindowSize != mSettings->WindowSize())
486 {
487 // possible only with advanced settings
489 XO("You must specify the same window size for steps 1 and 2."));
490 return false;
491 }
492 else if (mStatistics->mWindowTypes != mSettings->mWindowTypes)
493 {
494 // A warning only
496 XO("Warning: window types are not the same as for profiling."));
497 }
498
499 eWindowFunctions inWindowType, outWindowType;
500 switch (mSettings->mWindowTypes)
501 {
503 inWindowType = eWinFuncRectangular;
504 outWindowType = eWinFuncHann;
505 break;
507 inWindowType = eWinFuncHann;
508 outWindowType = eWinFuncRectangular;
509 break;
510 case WT_BLACKMAN_HANN:
511 inWindowType = eWinFuncBlackman;
512 outWindowType = eWinFuncHann;
513 break;
515 inWindowType = eWinFuncHamming;
516 outWindowType = eWinFuncRectangular;
517 break;
518 case WT_HAMMING_HANN:
519 inWindowType = eWinFuncHamming;
520 outWindowType = eWinFuncHann;
521 break;
522 default:
523 wxASSERT(false);
524 [[fallthrough]];
525 case WT_HANN_HANN:
526 inWindowType = outWindowType = eWinFuncHann;
527 break;
528 }
529 Worker worker { *this, *mSettings, *mStatistics
530#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
531 ,
532 mF0, mF1
533#endif
534 };
535 bool bGoodResult =
536 worker.Process(inWindowType, outWindowType, outputs.Get(), mT0, mT1);
537 const auto wasProfile = mSettings->mDoProfile;
538 if (mSettings->mDoProfile)
539 {
540 if (bGoodResult)
541 mSettings->mDoProfile =
542 false; // So that "repeat last effect" will reduce noise
543 else
544 mStatistics.reset(); // So that profiling must be done again before
545 // noise reduction
546 }
547 if (bGoodResult && !wasProfile)
548 outputs.Commit();
549
550 return bGoodResult;
551}
552
554{
555}
556
558 eWindowFunctions inWindowType, eWindowFunctions outWindowType,
559 TrackList& tracks, double inT0, double inT1)
560{
561 mProgressTrackCount = 0;
562 for (auto track : tracks.Selected<WaveTrack>())
563 {
564 mProgressWindowCount = 0;
565 if (track->GetRate() != mStatistics.mRate)
566 {
567 if (mDoProfile)
569 XO("All noise profile data must have the same sample rate."));
570 else
572 "The sample rate of the noise profile must match that of the sound to be processed."));
573 return false;
574 }
575
576 double trackStart = track->GetStartTime();
577 double trackEnd = track->GetEndTime();
578 double t0 = std::max(trackStart, inT0);
579 double t1 = std::min(trackEnd, inT1);
580
581 if (t1 > t0)
582 {
583 auto start = track->TimeToLongSamples(t0);
584 auto end = track->TimeToLongSamples(t1);
585 const auto len = end - start;
586 mLen = len;
587 const auto extra =
588 (mSettings.StepsPerWindow() - 1) * mSettings.SpectrumSize();
589 // Adjust denominator for presence or absence of padding,
590 // which makes the number of windows visited either more or less
591 // than the number of window steps in the data.
592 if (mDoProfile)
593 mLen -= extra;
594 else
595 mLen += extra;
596
597 auto t0 = track->LongSamplesToTime(start);
598 auto tLen = track->LongSamplesToTime(len);
599 std::optional<WaveTrack::Holder> ppTempTrack;
600 std::optional<ChannelGroup::ChannelIterator<WaveChannel>> pIter;
601 WaveTrack* pFirstTrack {};
602 if (!mSettings.mDoProfile)
603 {
604 ppTempTrack.emplace(track->EmptyCopy());
605 pFirstTrack = ppTempTrack->get();
606 pIter.emplace(pFirstTrack->Channels().begin());
607 }
608 for (const auto pChannel : track->Channels())
609 {
610 auto pOutputTrack = pIter ? *(*pIter)++ : nullptr;
611 MyTransformer transformer { *this,
612 pOutputTrack.get(),
613 !mSettings.mDoProfile,
614 inWindowType,
615 outWindowType,
616 mSettings.WindowSize(),
617 mSettings.StepsPerWindow(),
618 !mSettings.mDoProfile,
619 !mSettings.mDoProfile };
620 if (!transformer.Process(
621 Processor, *pChannel, mHistoryLen, start, len))
622 return false;
623 ++mProgressTrackCount;
624 }
625 if (ppTempTrack)
626 {
627 TrackSpectrumTransformer::PostProcess(*pFirstTrack, len);
628 constexpr auto preserveSplits = true;
629 constexpr auto merge = true;
630 track->ClearAndPaste(
631 t0, t0 + tLen, **ppTempTrack, preserveSplits, merge);
632 }
633 }
634 }
635
636 if (mDoProfile)
637 {
638 if (mStatistics.mTotalWindows == 0)
639 {
640 BasicUI::ShowMessageBox(XO("Selected noise profile is too short."));
641 return false;
642 }
643 }
644
645 return true;
646}
647
649{
650 // Given an array of gain mutipliers, average them
651 // GEOMETRICALLY. Don't multiply and take nth root --
652 // that may quickly cause underflows. Instead, average the logs.
653
654 if (mFreqSmoothingBins == 0)
655 return;
656
657 const auto spectrumSize = mSettings.SpectrumSize();
658
659 {
660 auto pScratch = mFreqSmoothingScratch.data();
661 std::fill(pScratch, pScratch + spectrumSize, 0.0f);
662 }
663
664 for (size_t ii = 0; ii < spectrumSize; ++ii)
665 gains[ii] = log(gains[ii]);
666
667 // ii must be signed
668 for (int ii = 0; ii < (int)spectrumSize; ++ii)
669 {
670 const int j0 = std::max(0, ii - (int)mFreqSmoothingBins);
671 const int j1 = std::min(spectrumSize - 1, ii + mFreqSmoothingBins);
672 for (int jj = j0; jj <= j1; ++jj)
673 {
674 mFreqSmoothingScratch[ii] += gains[jj];
675 }
676 mFreqSmoothingScratch[ii] /= (j1 - j0 + 1);
677 }
678
679 for (size_t ii = 0; ii < spectrumSize; ++ii)
680 gains[ii] = exp(mFreqSmoothingScratch[ii]);
681}
682
684 NoiseReductionBase& effect, const Settings& settings, Statistics& statistics
685#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
686 ,
687 double f0, double f1
688#endif
689 )
690 : mDoProfile { settings.mDoProfile }
691
692 , mEffect { effect }
693 , mSettings { settings }
694 , mStatistics { statistics }
695
696 , mFreqSmoothingScratch(mSettings.SpectrumSize())
697 , mFreqSmoothingBins { size_t(std::max(0.0, settings.mFreqSmoothingBands)) }
698 , mBinLow { 0 }
699 , mBinHigh { mSettings.SpectrumSize() }
700
701 , mNoiseReductionChoice { settings.mNoiseReductionChoice }
702 , mMethod { settings.mMethod }
703
704 // Sensitivity setting is a base 10 log, turn it into a natural log
705 , mNewSensitivity { settings.mNewSensitivity * log(10.0) }
706{
707 const auto sampleRate = mStatistics.mRate;
708
709#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
710 {
711 // mBinLow is inclusive, mBinHigh is exclusive, of
712 // the range of frequencies to affect. Include any
713 // bin that partly overlaps the selected range of frequencies.
714 const double bin = sampleRate / mWindowSize;
715 if (f0 >= 0.0)
716 mBinLow = floor(f0 / bin);
717 if (f1 >= 0.0)
718 mBinHigh = ceil(f1 / bin);
719 }
720#endif
721
722 const double noiseGain = -settings.mNoiseGain;
723 const unsigned nAttackBlocks =
725 const unsigned nReleaseBlocks =
727 // Applies to amplitudes, divide by 20:
728 mNoiseAttenFactor = DB_TO_LINEAR(noiseGain);
729 // Apply to gain factors which apply to amplitudes, divide by 20:
730 mOneBlockAttack = DB_TO_LINEAR(noiseGain / nAttackBlocks);
731 mOneBlockRelease = DB_TO_LINEAR(noiseGain / nReleaseBlocks);
732 // Applies to power, divide by 10:
734
737 std::max(2, (int)(minSignalTime * sampleRate / mSettings.StepSize())) :
739
741 wxASSERT(mCenter >= 1); // release depends on this assumption
742
743 if (mDoProfile)
744#ifdef OLD_METHOD_AVAILABLE
746#else
747 mHistoryLen = 1;
748#endif
749 else
750 {
751 // Allow long enough queue for sufficient inspection of the middle
752 // and for attack processing
753 // See ReduceNoise()
754 mHistoryLen = std::max(mNWindowsToExamine, mCenter + nAttackBlocks);
755 }
756}
757
759{
760 for (size_t ii = 0, nn = TotalQueueSize(); ii < nn; ++ii)
761 {
762 MyWindow& record = NthWindow(ii);
763 std::fill(record.mSpectrums.begin(), record.mSpectrums.end(), 0.0);
764 std::fill(
765 record.mGains.begin(), record.mGains.end(), mWorker.mNoiseAttenFactor);
766 }
768}
769
771{
772 auto& transformer = static_cast<MyTransformer&>(trans);
773 auto& worker = transformer.mWorker;
774 // Compute power spectrum in the newest window
775 {
776 auto& record = transformer.NthWindow(0);
777 float* pSpectrum = &record.mSpectrums[0];
778 const double dc = record.mRealFFTs[0];
779 *pSpectrum++ = dc * dc;
780 float *pReal = &record.mRealFFTs[1], *pImag = &record.mImagFFTs[1];
781 for (size_t nn = worker.mSettings.SpectrumSize() - 2; nn--;)
782 {
783 const double re = *pReal++, im = *pImag++;
784 *pSpectrum++ = re * re + im * im;
785 }
786 const double nyquist = record.mImagFFTs[0];
787 *pSpectrum = nyquist * nyquist;
788 }
789
790 if (worker.mDoProfile)
791 worker.GatherStatistics(transformer);
792 else
793 worker.ReduceNoise(transformer);
794
795 // Update the Progress meter, let user cancel
796 return !worker.mEffect.TrackProgress(
797 worker.mProgressTrackCount,
798 std::min(
799 1.0, ((++worker.mProgressWindowCount).as_double() *
800 worker.mSettings.StepSize()) /
801 worker.mLen.as_double()));
802}
803
805{
806 const auto windows = mStatistics.mTrackWindows;
807
808 // Combine averages in case of multiple profile tracks.
809 if (windows)
810 {
811 const auto multiplier = mStatistics.mTotalWindows;
812 const auto denom = windows + multiplier;
813 for (size_t ii = 0, nn = mStatistics.mMeans.size(); ii < nn; ++ii)
814 {
815 auto& mean = mStatistics.mMeans[ii];
816 auto& sum = mStatistics.mSums[ii];
817 mean = (mean * multiplier + sum) / denom;
818 // Reset for next track
819 sum = 0;
820 }
821 // Reset for next track
822 mStatistics.mTrackWindows = 0;
823 mStatistics.mTotalWindows = denom;
824 }
825}
826
828{
829 ++mStatistics.mTrackWindows;
830
831 {
832 // NEW statistics
833 auto pPower = transformer.NthWindow(0).mSpectrums.data();
834 auto pSum = mStatistics.mSums.data();
835 for (size_t jj = 0; jj < mSettings.SpectrumSize(); ++jj)
836 {
837 *pSum++ += *pPower++;
838 }
839 }
840
841#ifdef OLD_METHOD_AVAILABLE
842 // The noise threshold for each frequency is the maximum
843 // level achieved at that frequency for a minimum of
844 // mMinSignalBlocks blocks in a row - the max of a min.
845
846 auto finish = mHistoryLen;
847
848 {
849 // old statistics
850 auto pPower = NthWindow(0).mSpectrums.data();
851 auto pThreshold = mStatistics.mNoiseThreshold.data();
852 for (size_t jj = 0; jj < mSpectrumSize; ++jj)
853 {
854 float min = *pPower++;
855 for (unsigned ii = 1; ii < finish; ++ii)
856 min = std::min(min, NthWindow(ii).mSpectrums[jj]);
857 *pThreshold = std::max(*pThreshold, min);
858 ++pThreshold;
859 }
860 }
861#endif
862}
863
864// Return true iff the given band of the "center" window looks like noise.
865// Examine the band in a few neighboring windows to decide.
867 MyTransformer& transformer, unsigned nWindows, int band)
868{
869 switch (mMethod)
870 {
871#ifdef OLD_METHOD_AVAILABLE
872 case DM_OLD_METHOD:
873 {
874 float min = NthWindow(0).mSpectrums[band];
875 for (unsigned ii = 1; ii < nWindows; ++ii)
876 min = std::min(min, NthWindow(ii).mSpectrums[band]);
877 return min <= mOldSensitivityFactor * mStatistics.mNoiseThreshold[band];
878 }
879#endif
880 // New methods suppose an exponential distribution of power values
881 // in the noise; NEW sensitivity (which is nonnegative) is meant to be
882 // the negative of a log of probability (so the log is nonpositive)
883 // that noise strays above the threshold. Call that probability
884 // 1 - F. The quantile function of an exponential distribution is
885 // - log (1 - F) * mean. Thus simply multiply mean by sensitivity
886 // to get the threshold.
887 case DM_MEDIAN:
888 // This method examines the window and all other windows
889 // whose centers lie on or between its boundaries, and takes a median, to
890 // avoid being fooled by up and down excursions into
891 // either the mistake of classifying noise as not noise
892 // (leaving a musical noise chime), or the opposite
893 // (distorting the signal with a drop out).
894 if (nWindows <= 3)
895 // No different from second greatest.
896 goto secondGreatest;
897 else if (nWindows <= 5)
898 {
899 float greatest = 0.0, second = 0.0, third = 0.0;
900 for (unsigned ii = 0; ii < nWindows; ++ii)
901 {
902 const float power = transformer.NthWindow(ii).mSpectrums[band];
903 if (power >= greatest)
904 third = second, second = greatest, greatest = power;
905 else if (power >= second)
906 third = second, second = power;
907 else if (power >= third)
908 third = power;
909 }
910 return third <= mNewSensitivity * mStatistics.mMeans[band];
911 }
912 else
913 {
914 // not implemented
915 wxASSERT(false);
916 return true;
917 }
918 secondGreatest:
920 {
921 // This method just throws out the high outlier. It
922 // should be less prone to distortions and more prone to
923 // chimes.
924 float greatest = 0.0, second = 0.0;
925 for (unsigned ii = 0; ii < nWindows; ++ii)
926 {
927 const float power = transformer.NthWindow(ii).mSpectrums[band];
928 if (power >= greatest)
929 second = greatest, greatest = power;
930 else if (power >= second)
931 second = power;
932 }
933 return second <= mNewSensitivity * mStatistics.mMeans[band];
934 }
935 default:
936 wxASSERT(false);
937 return true;
938 }
939}
940
942{
943 auto historyLen = transformer.CurrentQueueSize();
944 auto nWindows = std::min<unsigned>(mNWindowsToExamine, historyLen);
945
946 const auto spectrumSize = mSettings.SpectrumSize();
947
948 if (mNoiseReductionChoice != NRC_ISOLATE_NOISE)
949 {
950 auto& record = transformer.NthWindow(0);
951 // Default all gains to the reduction factor,
952 // until we decide to raise some of them later
953 float* pGain = &record.mGains[0];
954 std::fill(pGain, pGain + spectrumSize, mNoiseAttenFactor);
955 }
956
957 // Raise the gain for elements in the center of the sliding history
958 // or, if isolating noise, zero out the non-noise
959 if (nWindows > mCenter)
960 {
961 auto pGain = transformer.NthWindow(mCenter).mGains.data();
962 if (mNoiseReductionChoice == NRC_ISOLATE_NOISE)
963 {
964 // All above or below the selected frequency range is non-noise
965 std::fill(pGain, pGain + mBinLow, 0.0f);
966 std::fill(pGain + mBinHigh, pGain + spectrumSize, 0.0f);
967 pGain += mBinLow;
968 for (size_t jj = mBinLow; jj < mBinHigh; ++jj)
969 {
970 const bool isNoise = Classify(transformer, nWindows, jj);
971 *pGain++ = isNoise ? 1.0 : 0.0;
972 }
973 }
974 else
975 {
976 // All above or below the selected frequency range is non-noise
977 std::fill(pGain, pGain + mBinLow, 1.0f);
978 std::fill(pGain + mBinHigh, pGain + spectrumSize, 1.0f);
979 pGain += mBinLow;
980 for (size_t jj = mBinLow; jj < mBinHigh; ++jj)
981 {
982 const bool isNoise = Classify(transformer, nWindows, jj);
983 if (!isNoise)
984 *pGain = 1.0;
985 ++pGain;
986 }
987 }
988 }
989
990 if (mNoiseReductionChoice != NRC_ISOLATE_NOISE)
991 {
992 // In each direction, define an exponential decay of gain from the
993 // center; make actual gains the maximum of mNoiseAttenFactor, and
994 // the decay curve, and their prior values.
995
996 // First, the attack, which goes backward in time, which is,
997 // toward higher indices in the queue.
998 for (size_t jj = 0; jj < spectrumSize; ++jj)
999 {
1000 for (unsigned ii = mCenter + 1; ii < historyLen; ++ii)
1001 {
1002 const float minimum = std::max(
1003 mNoiseAttenFactor,
1004 transformer.NthWindow(ii - 1).mGains[jj] * mOneBlockAttack);
1005 float& gain = transformer.NthWindow(ii).mGains[jj];
1006 if (gain < minimum)
1007 gain = minimum;
1008 else
1009 // We can stop now, our attack curve is intersecting
1010 // the release curve of some window previously processed.
1011 break;
1012 }
1013 }
1014
1015 // Now, release. We need only look one window ahead. This part will
1016 // be visited again when we examine the next window, and
1017 // carry the decay further.
1018 {
1019 auto pNextGain = transformer.NthWindow(mCenter - 1).mGains.data();
1020 auto pThisGain = transformer.NthWindow(mCenter).mGains.data();
1021 for (auto nn = mSettings.SpectrumSize(); nn--;)
1022 {
1023 *pNextGain = std::max(
1024 *pNextGain,
1025 std::max(mNoiseAttenFactor, *pThisGain++ * mOneBlockRelease));
1026 ++pNextGain;
1027 }
1028 }
1029 }
1030
1031 if (transformer.QueueIsFull())
1032 {
1033 auto& record = transformer.NthWindow(historyLen - 1); // end of the queue
1034 const auto last = mSettings.SpectrumSize() - 1;
1035
1036 if (mNoiseReductionChoice != NRC_ISOLATE_NOISE)
1037 // Apply frequency smoothing to output gain
1038 // Gains are not less than mNoiseAttenFactor
1039 ApplyFreqSmoothing(record.mGains);
1040
1041 // Apply gain to FFT
1042 {
1043 const float* pGain = &record.mGains[1];
1044 float* pReal = &record.mRealFFTs[1];
1045 float* pImag = &record.mImagFFTs[1];
1046 auto nn = mSettings.SpectrumSize() - 2;
1047 if (mNoiseReductionChoice == NRC_LEAVE_RESIDUE)
1048 {
1049 for (; nn--;)
1050 {
1051 // Subtract the gain we would otherwise apply from 1, and
1052 // negate that to flip the phase.
1053 const double gain = *pGain++ - 1.0;
1054 *pReal++ *= gain;
1055 *pImag++ *= gain;
1056 }
1057 record.mRealFFTs[0] *= (record.mGains[0] - 1.0);
1058 // The Fs/2 component is stored as the imaginary part of the DC
1059 // component
1060 record.mImagFFTs[0] *= (record.mGains[last] - 1.0);
1061 }
1062 else
1063 {
1064 for (; nn--;)
1065 {
1066 const double gain = *pGain++;
1067 *pReal++ *= gain;
1068 *pImag++ *= gain;
1069 }
1070 record.mRealFFTs[0] *= record.mGains[0];
1071 // The Fs/2 component is stored as the imaginary part of the DC
1072 // component
1073 record.mImagFFTs[0] *= record.mGains[last];
1074 }
1075 }
1076 }
1077}
1078
1080{
1081 if (mWorker.mDoProfile)
1082 mWorker.FinishTrackStatistics();
1084}
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
int min(int a, int b)
EffectType
@ EffectTypeProcess
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")
static ProjectFileIORegistry::AttributeWriterEntry entry
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:338
std::vector< float > FloatVector
@ NRC_REDUCE_NOISE
@ NRC_LEAVE_RESIDUE
@ NRC_ISOLATE_NOISE
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
const auto tracks
static Settings & settings()
Definition: TrackInfo.cpp:51
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
double mT1
Definition: EffectBase.h:123
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
double mF0
Definition: EffectBase.h:105
double mF1
Definition: EffectBase.h:106
double mT0
Definition: EffectBase.h:122
Performs effect computation.
Use this object to copy the input tracks to tentative outputTracks.
bool Validate(NoiseReductionBase *effect) const
Statistics(size_t spectrumSize, double rate, int windowTypes)
void ReduceNoise(MyTransformer &transformer)
Worker(NoiseReductionBase &effect, const Settings &settings, Statistics &statistics)
void GatherStatistics(MyTransformer &transformer)
bool Process(eWindowFunctions inWindowType, eWindowFunctions outWindowType, TrackList &tracks, double mT0, double mT1)
bool Classify(MyTransformer &transformer, unsigned nWindows, int band)
static bool Processor(SpectrumTransformer &transformer)
void ApplyFreqSmoothing(FloatVector &gains)
NoiseReductionBase::Statistics Statistics
NoiseReductionBase::Settings Settings
A two-pass effect to reduce background noise.
ComponentInterfaceSymbol GetSymbol() const override
bool Process(EffectInstance &instance, EffectSettings &settings) override
std::unique_ptr< Settings > mSettings
static const ComponentInterfaceSymbol Symbol
std::unique_ptr< Statistics > mStatistics
TranslatableString GetDescription() const override
EffectType GetType() const override
Type determines how it behaves.
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?
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
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
const struct anonymous_namespace{NoiseReductionBase.cpp}::WindowTypesInfo windowTypesInfo[WT_N_WINDOW_TYPES]
const struct anonymous_namespace{NoiseReductionBase.cpp}::DiscriminationMethodInfo discriminationMethodInfo[DM_N_METHODS]
void readPrefs(StructureType *structure, const wxString &prefix, const PrefsTableEntry< StructureType, FieldType > *fields, size_t numFields)
void writePrefs(const StructureType *structure, const wxString &prefix, const PrefsTableEntry< StructureType, FieldType > *fields, size_t numFields)
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.
bool DoFinish() override
Called after the last call to ProcessWindow().
MyTransformer(NoiseReductionBase::Worker &worker, WaveChannel *pOutputTrack, bool needsOutput, eWindowFunctions inWindowType, eWindowFunctions outWindowType, size_t windowSize, unsigned stepsPerWindow, bool leadingPadding, bool trailingPadding)
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.
NoiseReductionBase::Worker & mWorker
MyWindow & NthWindow(int nn)
const wxChar * name
FieldTypeStructureType::* MemberPointer
MemberPointer field
FieldType defaultValue