bb26b2f2c4
... by passing parent as reference, not pointer, and testing in the dialog factory function. This is important so that we know the lifetime of an effect dialog, even when it is non-modal, is bounded by the lifetime of the associated project.
1918 lines
59 KiB
C++
1918 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 "../Audacity.h"
|
|
#include "NoiseReduction.h"
|
|
|
|
#include "../Experimental.h"
|
|
|
|
#include "EffectManager.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, TrackFactory &factory,
|
|
TrackList &tracks, double mT0, double mT1);
|
|
|
|
private:
|
|
bool ProcessOne(EffectNoiseReduction &effect,
|
|
Statistics &statistics,
|
|
TrackFactory &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()
|
|
};
|
|
|
|
EffectNoiseReduction::EffectNoiseReduction()
|
|
: mSettings(std::make_unique<EffectNoiseReduction::Settings>())
|
|
{
|
|
Init();
|
|
}
|
|
|
|
EffectNoiseReduction::~EffectNoiseReduction()
|
|
{
|
|
}
|
|
|
|
// ComponentInterface implementation
|
|
|
|
ComponentInterfaceSymbol EffectNoiseReduction::GetSymbol()
|
|
{
|
|
return NOISEREDUCTION_PLUGIN_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, the only case in which we can
|
|
// get here without any wavetracks.
|
|
return mSettings->PromptUser(this, parent,
|
|
bool(mStatistics), (GetNumWaveTracks() == 0));
|
|
}
|
|
|
|
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, TrackFactory &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, TrackFactory &factory,
|
|
int count, WaveTrack * track, sampleCount start, sampleCount len)
|
|
{
|
|
if (track == NULL)
|
|
return false;
|
|
|
|
StartNewTrack();
|
|
|
|
WaveTrack::Holder outputTrack;
|
|
if(!mDoProfile)
|
|
outputTrack = factory.NewWaveTrack(track->GetSampleFormat(), track->GetRate());
|
|
|
|
auto bufferSize = track->GetMaxBlockSize();
|
|
FloatVector buffer(bufferSize);
|
|
|
|
bool bLoopSuccess = true;
|
|
auto samplePos = start;
|
|
while (bLoopSuccess && samplePos < start + len) {
|
|
//Get a blockSize of samples (smaller than the size of the buffer)
|
|
const auto blockSize = limitSampleBufferSize(
|
|
track->GetBestBlockSize(samplePos),
|
|
start + len - samplePos
|
|
);
|
|
|
|
//Get the samples from the track and put them in the buffer
|
|
track->Get((samplePtr)&buffer[0], floatSample, samplePos, blockSize);
|
|
samplePos += blockSize;
|
|
|
|
mInSampleCount += blockSize;
|
|
ProcessSamples(statistics, outputTrack.get(), blockSize, &buffer[0]);
|
|
|
|
// Update the Progress meter, let user cancel
|
|
bLoopSuccess =
|
|
!effect.TrackProgress(count,
|
|
( samplePos - start ).as_double() /
|
|
len.as_double() );
|
|
}
|
|
|
|
if (bLoopSuccess) {
|
|
if (mDoProfile)
|
|
FinishTrackStatistics(statistics);
|
|
else
|
|
FinishTrack(statistics, &*outputTrack);
|
|
}
|
|
|
|
if (bLoopSuccess && !mDoProfile) {
|
|
// Flush the output WaveTrack (since it's buffered)
|
|
outputTrack->Flush();
|
|
|
|
// Take the output track and insert it in place of the original
|
|
// sample data (as operated on -- this may not match mT0/mT1)
|
|
double t0 = outputTrack->LongSamplesToTime(start);
|
|
double tLen = outputTrack->LongSamplesToTime(len);
|
|
// Filtering effects always end up with more data than they started with. Delete this 'tail'.
|
|
outputTrack->HandleClear(tLen, outputTrack->GetEndTime(), false, false);
|
|
track->ClearAndPaste(t0, t0 + tLen, &*outputTrack, true, false);
|
|
}
|
|
|
|
return bLoopSuccess;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// EffectNoiseReduction::Dialog
|
|
//----------------------------------------------------------------------------
|
|
|
|
enum {
|
|
ID_BUTTON_GETPROFILE = 10001,
|
|
ID_RADIOBUTTON_KEEPSIGNAL,
|
|
#ifdef ISOLATE_CHOICE
|
|
ID_RADIOBUTTON_KEEPNOISE,
|
|
#endif
|
|
#ifdef RESIDUE_CHOICE
|
|
ID_RADIOBUTTON_RESIDUE,
|
|
#endif
|
|
|
|
#ifdef ADVANCED_SETTINGS
|
|
ID_CHOICE_METHOD,
|
|
#endif
|
|
|
|
// Slider/text pairs
|
|
ID_GAIN_SLIDER,
|
|
ID_GAIN_TEXT,
|
|
|
|
ID_NEW_SENSITIVITY_SLIDER,
|
|
ID_NEW_SENSITIVITY_TEXT,
|
|
|
|
#ifdef ATTACK_AND_RELEASE
|
|
ID_ATTACK_TIME_SLIDER,
|
|
ID_ATTACK_TIME_TEXT,
|
|
|
|
ID_RELEASE_TIME_SLIDER,
|
|
ID_RELEASE_TIME_TEXT,
|
|
#endif
|
|
|
|
ID_FREQ_SLIDER,
|
|
ID_FREQ_TEXT,
|
|
|
|
END_OF_BASIC_SLIDERS,
|
|
|
|
#ifdef ADVANCED_SETTINGS
|
|
ID_OLD_SENSITIVITY_SLIDER = END_OF_BASIC_SLIDERS,
|
|
ID_OLD_SENSITIVITY_TEXT,
|
|
|
|
END_OF_ADVANCED_SLIDERS,
|
|
END_OF_SLIDERS = END_OF_ADVANCED_SLIDERS,
|
|
#else
|
|
END_OF_SLIDERS = END_OF_BASIC_SLIDERS,
|
|
#endif
|
|
|
|
FIRST_SLIDER = ID_GAIN_SLIDER,
|
|
};
|
|
|
|
namespace {
|
|
|
|
struct ControlInfo {
|
|
typedef double (EffectNoiseReduction::Settings::*MemberPointer);
|
|
|
|
double Value(long sliderSetting) const
|
|
{
|
|
return
|
|
valueMin +
|
|
(double(sliderSetting) / sliderMax) * (valueMax - valueMin);
|
|
}
|
|
|
|
long SliderSetting(double value) const
|
|
{
|
|
return TrapLong(
|
|
0.5 + sliderMax * (value - valueMin) / (valueMax - valueMin),
|
|
0, sliderMax);
|
|
}
|
|
|
|
wxString Text(double value) const
|
|
{
|
|
if (formatAsInt)
|
|
return wxString::Format(format, (int)(value));
|
|
else
|
|
return wxString::Format(format, value);
|
|
}
|
|
|
|
void CreateControls(int id, ShuttleGui &S) const
|
|
{
|
|
wxTextCtrl *const text = S.Id(id + 1)
|
|
.Validator<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,
|
|
XO("&Noise reduction (dB):"), XO("Noise reduction")),
|
|
ControlInfo(&EffectNoiseReduction::Settings::mNewSensitivity,
|
|
0.0, 24.0, 48, wxT("%.2f"), false,
|
|
XO("&Sensitivity:"), XO("Sensitivity")),
|
|
#ifdef ATTACK_AND_RELEASE
|
|
ControlInfo(&EffectNoiseReduction::Settings::mAttackTime,
|
|
0, 1.0, 100, wxT("%.2f"), false,
|
|
XO("Attac&k time (secs):"), XO("Attack time")),
|
|
ControlInfo(&EffectNoiseReduction::Settings::mReleaseTime,
|
|
0, 1.0, 100, wxT("%.2f"), false,
|
|
XO("R&elease time (secs):"), XO("Release time")),
|
|
#endif
|
|
ControlInfo(&EffectNoiseReduction::Settings::mFreqSmoothingBands,
|
|
0, 12, 12, wxT("%d"), true,
|
|
XO("&Frequency smoothing (bands):"), XO("Frequency smoothing")),
|
|
|
|
#ifdef ADVANCED_SETTINGS
|
|
ControlInfo(&EffectNoiseReduction::Settings::mOldSensitivity,
|
|
-20.0, 20.0, 4000, wxT("%.2f"), false,
|
|
XO("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(XO("&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(XO("Noise:"));
|
|
mKeepSignal = S.Id(ID_RADIOBUTTON_KEEPSIGNAL)
|
|
.AddRadioButton(XO("Re&duce")); /* i18n-hint: Translate differently from "Residue" ! */
|
|
#ifdef ISOLATE_CHOICE
|
|
mKeepNoise = S.Id(ID_RADIOBUTTON_KEEPNOISE)
|
|
.AddRadioButtonToGroup(XO("&Isolate"));
|
|
#endif
|
|
#ifdef RESIDUE_CHOICE
|
|
mResidue = S.Id(ID_RADIOBUTTON_RESIDUE)
|
|
.AddRadioButtonToGroup(XO("Resid&ue")); /* i18n-hint: Means the difference between effect and original sound. Translate differently from "Reduce" ! */
|
|
#endif
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
S.EndStatic();
|
|
|
|
|
|
#ifdef ADVANCED_SETTINGS
|
|
S.StartStatic(XO("Advanced Settings"));
|
|
{
|
|
S.StartMultiColumn(2);
|
|
{
|
|
S.TieChoice(XO("&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(XO("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(XO("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(XO("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));
|
|
}
|
|
|