audacia/src/effects/NoiseReduction.cpp

1924 lines
59 KiB
C++

/**********************************************************************
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 "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 <algorithm>
#include <vector>
#include <math.h>
#if defined(__WXMSW__) && !defined(__CYGWIN__)
#include <float.h>
#define finite(x) _finite(x)
#endif
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/dialog.h>
#include <wx/radiobut.h>
#include <wx/slider.h>
#include <wx/valtext.h>
#include <wx/textctrl.h>
#include <wx/sizer.h>
// 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<float> 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<std::unique_ptr<Record>> 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<EffectNoiseReduction::Settings>())
{
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 <typename StructureType, typename FieldType>
struct PrefsTableEntry {
typedef FieldType (StructureType::*MemberPointer);
MemberPointer field;
const wxChar *name;
FieldType defaultValue;
};
template <typename StructureType, typename FieldType>
void readPrefs(
StructureType *structure, const wxString &prefix,
const PrefsTableEntry<StructureType, FieldType> *fields, int numFields)
{
for (int ii = 0; ii < numFields; ++ii) {
const PrefsTableEntry<StructureType, FieldType> &entry = fields[ii];
gPrefs->Read(prefix + entry.name, &(structure->*(entry.field)),
entry.defaultValue);
}
}
template <typename StructureType, typename FieldType>
void writePrefs(
StructureType *structure, const wxString &prefix,
const PrefsTableEntry<StructureType, FieldType> *fields, int numFields)
{
for (int ii = 0; ii < numFields; ++ii) {
const PrefsTableEntry<StructureType, FieldType> &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<Settings, double> 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<Settings, int> 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<Statistics>
(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<Record>(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->GetFloats(&buffer[0], 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<FloatingPointValidator<double>>(
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<wxChoice*>(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<wxSlider*>(wxWindow::FindWindowById(id, this));
wxTextCtrl* text =
static_cast<wxTextCtrl*>(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<wxTextCtrl*>(wxWindow::FindWindowById(id, this));
wxSlider* slider =
static_cast<wxSlider*>(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<wxSlider*>(wxWindow::FindWindowById(id, this));
wxTextCtrl* text =
static_cast<wxTextCtrl*>(wxWindow::FindWindowById(id + 1, this));
double &field = mTempSettings.*(info.field);
field = info.Value(slider->GetValue());
text->SetValue(info.Text(field));
}