audacia/src/effects/NoiseRemoval.cpp
Paul Licameli c2feee6cea Bug2346: Complete fix...
... without making undesirable dependency cycles.

Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report.  (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)

Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.

NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.

Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.

Also, in mix-and-render, where other logic decides whether to copy colors
afterward.

See commit a9658e6ef7
2020-03-10 22:32:23 -04:00

849 lines
26 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
NoiseRemoval.cpp
Dominic Mazzoni
*******************************************************************//**
\class EffectNoiseRemoval
\brief A two-pass effect to remove 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 - specifically the maximum level achieved by
at least (n) sampling windows in a row, for various values of (n).
During the noise removal 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, otherwise
the gain is set lower (e.g. -18 dB), to suppress the noise.
Then frequency-smoothing is applied so that a single frequency is
never suppressed or boosted in isolation, and then time-smoothing
is applied so that the gain for each frequency band moves slowly.
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, followed by a Hann window;
the output signal is then pieced together using overlap/add of
half the window size.
*//****************************************************************//**
\class NoiseRemovalDialog
\brief Dialog used with EffectNoiseRemoval
*//*******************************************************************/
#include "../Audacity.h"
#include "NoiseRemoval.h"
#include "../Experimental.h"
#if !defined(EXPERIMENTAL_NOISE_REDUCTION)
#include "LoadEffects.h"
#include "../WaveTrack.h"
#include "../Prefs.h"
#include "../FileNames.h"
#include "../ShuttleGui.h"
#include <math.h>
#if defined(__WXMSW__) && !defined(__CYGWIN__)
#include <float.h>
#define finite(x) _finite(x)
#endif
#include <wx/file.h>
#include <wx/ffile.h>
#include <wx/bitmap.h>
#include <wx/brush.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/radiobut.h>
#include <wx/dcmemory.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/valtext.h>
#include "../PlatformCompatibility.h"
const ComponentInterfaceSymbol EffectNoiseRemoval::Symbol
{ XO("Noise Removal") };
namespace{ BuiltinEffectsModule::Registration< EffectNoiseRemoval > reg; }
EffectNoiseRemoval::EffectNoiseRemoval()
{
mWindowSize = 2048;
mSpectrumSize = 1 + mWindowSize / 2;
gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseSensitivity"),
&mSensitivity, 0.0);
gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseGain"),
&mNoiseGain, -24.0);
gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseFreqSmoothing"),
&mFreqSmoothingHz, 150.0);
gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseAttackDecayTime"),
&mAttackDecayTime, 0.15);
gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseLeaveNoise"),
&mbLeaveNoise, false);
// mbLeaveNoise = false;
mMinSignalTime = 0.05f;
mHasProfile = false;
mDoProfile = true;
mNoiseThreshold.reinit(mSpectrumSize);
Init();
}
EffectNoiseRemoval::~EffectNoiseRemoval()
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectNoiseRemoval::GetSymbol()
{
return Symbol;
}
TranslatableString EffectNoiseRemoval::GetDescription()
{
return XO("Removes constant background noise such as fans, tape noise, or hums");
}
// EffectDefinitionInterface implementation
EffectType EffectNoiseRemoval::GetType()
{
return EffectTypeProcess;
}
bool EffectNoiseRemoval::SupportsAutomation()
{
return false;
}
// Effect implementation
#define MAX_NOISE_LEVEL 30
bool EffectNoiseRemoval::Init()
{
mLevel = gPrefs->Read(wxT("/Effects/NoiseRemoval/Noise_Level"), 3L);
if ((mLevel < 0) || (mLevel > MAX_NOISE_LEVEL)) { // corrupted Prefs?
mLevel = 0; //Off-skip
gPrefs->Write(wxT("/Effects/NoiseRemoval/Noise_Level"), mLevel);
}
return gPrefs->Flush();
}
bool EffectNoiseRemoval::CheckWhetherSkipEffect()
{
return (mLevel == 0);
}
bool EffectNoiseRemoval::ShowInterface(
wxWindow &parent, const EffectDialogFactory &, bool forceModal )
{
// to do: use forceModal correctly
NoiseRemovalDialog dlog(this, &parent);
dlog.mSensitivity = mSensitivity;
dlog.mGain = -mNoiseGain;
dlog.mFreq = mFreqSmoothingHz;
dlog.mTime = mAttackDecayTime;
dlog.mbLeaveNoise = mbLeaveNoise;
dlog.mKeepSignal->SetValue(!mbLeaveNoise);
dlog.mKeepNoise->SetValue(mbLeaveNoise);
// We may want to twiddle the levels if we are setting
// from an automation dialog
bool bAllowTwiddleSettings = forceModal;
if (mHasProfile || bAllowTwiddleSettings) {
dlog.m_pButton_Preview->Enable(GetNumWaveTracks() != 0);
dlog.m_pButton_RemoveNoise->SetDefault();
} else {
dlog.m_pButton_Preview->Enable(false);
dlog.m_pButton_RemoveNoise->Enable(false);
}
dlog.TransferDataToWindow();
dlog.mKeepNoise->SetValue(dlog.mbLeaveNoise);
dlog.CentreOnParent();
dlog.ShowModal();
if (dlog.GetReturnCode() == 0) {
return false;
}
mSensitivity = dlog.mSensitivity;
mNoiseGain = -dlog.mGain;
mFreqSmoothingHz = dlog.mFreq;
mAttackDecayTime = dlog.mTime;
mbLeaveNoise = dlog.mbLeaveNoise;
gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseSensitivity"), mSensitivity);
gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseGain"), mNoiseGain);
gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseFreqSmoothing"), mFreqSmoothingHz);
gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseAttackDecayTime"), mAttackDecayTime);
gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseLeaveNoise"), mbLeaveNoise);
mDoProfile = (dlog.GetReturnCode() == 1);
return gPrefs->Flush();
}
bool EffectNoiseRemoval::Process()
{
Initialize();
// This same code will both remove noise and profile it,
// depending on 'mDoProfile'
this->CopyInputTracks(); // Set up mOutputTracks.
bool bGoodResult = true;
int count = 0;
for( auto track : mOutputTracks->Selected< WaveTrack >() ) {
double trackStart = track->GetStartTime();
double trackEnd = track->GetEndTime();
double t0 = mT0 < trackStart? trackStart: mT0;
double t1 = mT1 > trackEnd? trackEnd: mT1;
if (t1 > t0) {
auto start = track->TimeToLongSamples(t0);
auto end = track->TimeToLongSamples(t1);
auto len = end - start;
if (!ProcessOne(count, track, start, len)) {
bGoodResult = false;
break;
}
}
count++;
}
if (bGoodResult && mDoProfile) {
mHasProfile = true;
mDoProfile = false;
}
this->ReplaceProcessedTracks(bGoodResult);
return bGoodResult;
}
void EffectNoiseRemoval::ApplyFreqSmoothing(float *spec)
{
Floats tmp{ mSpectrumSize };
int j, j0, j1;
for(int i = 0; i < mSpectrumSize; i++) {
j0 = wxMax(0, i - mFreqSmoothingBins);
j1 = wxMin(mSpectrumSize-1, i + mFreqSmoothingBins);
tmp[i] = 0.0;
for(j = j0; j <= j1; j++) {
tmp[i] += spec[j];
}
tmp[i] /= (j1 - j0 + 1);
}
for(size_t i = 0; i < mSpectrumSize; i++)
spec[i] = tmp[i];
}
void EffectNoiseRemoval::Initialize()
{
mSampleRate = mProjectRate;
mFreqSmoothingBins = (int)(mFreqSmoothingHz * mWindowSize / mSampleRate);
mAttackDecayBlocks = 1 +
(int)(mAttackDecayTime * mSampleRate / (mWindowSize / 2));
mNoiseAttenFactor = DB_TO_LINEAR(mNoiseGain);
mOneBlockAttackDecay = DB_TO_LINEAR(mNoiseGain / mAttackDecayBlocks);
// Applies to power, divide by 10:
mSensitivityFactor = pow(10.0, mSensitivity/10.0);
mMinSignalBlocks =
(int)(mMinSignalTime * mSampleRate / (mWindowSize / 2));
if( mMinSignalBlocks < 1 )
mMinSignalBlocks = 1;
mHistoryLen = (2 * mAttackDecayBlocks) - 1;
if (mHistoryLen < mMinSignalBlocks)
mHistoryLen = mMinSignalBlocks;
mSpectrums.reinit(mHistoryLen, mSpectrumSize);
mGains.reinit(mHistoryLen, mSpectrumSize);
mRealFFTs.reinit(mHistoryLen, mSpectrumSize);
mImagFFTs.reinit(mHistoryLen, mSpectrumSize);
// Initialize the FFT
hFFT = GetFFT(mWindowSize);
mFFTBuffer.reinit(mWindowSize);
mInWaveBuffer.reinit(mWindowSize);
mWindow.reinit(mWindowSize);
mOutOverlapBuffer.reinit(mWindowSize);
// Create a Hann window function
for(size_t i=0; i<mWindowSize; i++)
mWindow[i] = 0.5 - 0.5 * cos((2.0*M_PI*i) / mWindowSize);
if (mDoProfile) {
for (size_t i = 0; i < mSpectrumSize; i++)
mNoiseThreshold[i] = float(0);
}
}
void EffectNoiseRemoval::End()
{
hFFT.reset();
if (mDoProfile) {
ApplyFreqSmoothing(mNoiseThreshold.get());
}
mSpectrums.reset();
mGains.reset();
mRealFFTs.reset();
mImagFFTs.reset();
mFFTBuffer.reset();
mInWaveBuffer.reset();
mWindow.reset();
mOutOverlapBuffer.reset();
mOutputTrack.reset();
}
void EffectNoiseRemoval::StartNewTrack()
{
for(size_t i = 0; i < mHistoryLen; i++) {
for(size_t j = 0; j < mSpectrumSize; j++) {
mSpectrums[i][j] = 0;
mGains[i][j] = mNoiseAttenFactor;
mRealFFTs[i][j] = 0.0;
mImagFFTs[i][j] = 0.0;
}
}
for(size_t j = 0; j < mWindowSize; j++)
mOutOverlapBuffer[j] = 0.0;
mInputPos = 0;
mInSampleCount = 0;
mOutSampleCount = -(int)((mWindowSize / 2) * (mHistoryLen - 1));
}
void EffectNoiseRemoval::ProcessSamples(size_t len, float *buffer)
{
while(len && mOutSampleCount < mInSampleCount) {
size_t avail = wxMin(len, mWindowSize - mInputPos);
for(size_t i = 0; i < avail; i++)
mInWaveBuffer[mInputPos + i] = buffer[i];
buffer += avail;
len -= avail;
mInputPos += avail;
if (mInputPos == int(mWindowSize)) {
FillFirstHistoryWindow();
if (mDoProfile)
GetProfile();
else
RemoveNoise();
RotateHistoryWindows();
// Rotate halfway for overlap-add
for(size_t i = 0; i < mWindowSize / 2; i++) {
mInWaveBuffer[i] = mInWaveBuffer[i + mWindowSize / 2];
}
mInputPos = mWindowSize / 2;
}
}
}
void EffectNoiseRemoval::FillFirstHistoryWindow()
{
for(size_t i = 0; i < mWindowSize; i++)
mFFTBuffer[i] = mInWaveBuffer[i];
RealFFTf(mFFTBuffer.get(), hFFT.get());
for(size_t i = 1; i + 1 < mSpectrumSize; i++) {
mRealFFTs[0][i] = mFFTBuffer[hFFT->BitReversed[i] ];
mImagFFTs[0][i] = mFFTBuffer[hFFT->BitReversed[i]+1];
mSpectrums[0][i] = mRealFFTs[0][i]*mRealFFTs[0][i] + mImagFFTs[0][i]*mImagFFTs[0][i];
mGains[0][i] = mNoiseAttenFactor;
}
// DC and Fs/2 bins need to be handled specially
mSpectrums[0][0] = mFFTBuffer[0]*mFFTBuffer[0];
mSpectrums[0][mSpectrumSize-1] = mFFTBuffer[1]*mFFTBuffer[1];
mGains[0][0] = mNoiseAttenFactor;
mGains[0][mSpectrumSize-1] = mNoiseAttenFactor;
}
namespace {
inline void Rotate(ArraysOf<float> &arrays, size_t historyLen)
{
Floats temp = std::move( arrays[ historyLen - 1 ] );
for ( size_t nn = historyLen - 1; nn--; )
arrays[ nn + 1 ] = std::move( arrays[ nn ] );
arrays[0] = std::move( temp );
}
}
void EffectNoiseRemoval::RotateHistoryWindows()
{
// Remember the last window so we can reuse it
Rotate(mSpectrums, mHistoryLen);
Rotate(mGains, mHistoryLen);
Rotate(mRealFFTs, mHistoryLen);
Rotate(mImagFFTs, mHistoryLen);
}
void EffectNoiseRemoval::FinishTrack()
{
// 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 mWindowSize/2 extra samples at the end.
// We'll DELETE them later in ProcessOne.
Floats empty{ mWindowSize / 2 };
for(size_t i = 0; i < mWindowSize / 2; i++)
empty[i] = 0.0;
while (mOutSampleCount < mInSampleCount) {
ProcessSamples(mWindowSize / 2, empty.get());
}
}
void EffectNoiseRemoval::GetProfile()
{
// 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.
int start = mHistoryLen - mMinSignalBlocks;
int finish = mHistoryLen;
int i;
for (size_t j = 0; j < mSpectrumSize; j++) {
float min = mSpectrums[start][j];
for (i = start+1; i < finish; i++) {
if (mSpectrums[i][j] < min)
min = mSpectrums[i][j];
}
if (min > mNoiseThreshold[j])
mNoiseThreshold[j] = min;
}
mOutSampleCount += mWindowSize / 2; // what is this for? Not used when we are getting the profile?
}
void EffectNoiseRemoval::RemoveNoise()
{
size_t center = mHistoryLen / 2;
size_t start = center - mMinSignalBlocks/2;
size_t finish = start + mMinSignalBlocks;
// Raise the gain for elements in the center of the sliding history
for (size_t j = 0; j < mSpectrumSize; j++) {
float min = mSpectrums[start][j];
for (size_t i = start+1; i < finish; i++) {
if (mSpectrums[i][j] < min)
min = mSpectrums[i][j];
}
if (min > mSensitivityFactor * mNoiseThreshold[j] && mGains[center][j] < 1.0) {
if (mbLeaveNoise) mGains[center][j] = 0.0;
else mGains[center][j] = 1.0;
} else {
if (mbLeaveNoise) mGains[center][j] = 1.0;
}
}
// Decay the gain in both directions;
// note that mOneBlockAttackDecay is less than 1.0
// of linear attenuation per block
for (size_t j = 0; j < mSpectrumSize; j++) {
for (size_t i = center + 1; i < mHistoryLen; i++) {
if (mGains[i][j] < mGains[i - 1][j] * mOneBlockAttackDecay)
mGains[i][j] = mGains[i - 1][j] * mOneBlockAttackDecay;
if (mGains[i][j] < mNoiseAttenFactor)
mGains[i][j] = mNoiseAttenFactor;
}
for (size_t i = center; i--;) {
if (mGains[i][j] < mGains[i + 1][j] * mOneBlockAttackDecay)
mGains[i][j] = mGains[i + 1][j] * mOneBlockAttackDecay;
if (mGains[i][j] < mNoiseAttenFactor)
mGains[i][j] = mNoiseAttenFactor;
}
}
// Apply frequency smoothing to output gain
int out = mHistoryLen - 1; // end of the queue
ApplyFreqSmoothing(mGains[out].get());
// Apply gain to FFT
for (size_t j = 0; j < (mSpectrumSize-1); j++) {
mFFTBuffer[j*2 ] = mRealFFTs[out][j] * mGains[out][j];
mFFTBuffer[j*2+1] = mImagFFTs[out][j] * mGains[out][j];
}
// The Fs/2 component is stored as the imaginary part of the DC component
mFFTBuffer[1] = mRealFFTs[out][mSpectrumSize-1] * mGains[out][mSpectrumSize-1];
// Invert the FFT into the output buffer
InverseRealFFTf(mFFTBuffer.get(), hFFT.get());
// Overlap-add
for(size_t j = 0; j < (mSpectrumSize-1); j++) {
mOutOverlapBuffer[j*2 ] += mFFTBuffer[hFFT->BitReversed[j] ] * mWindow[j*2 ];
mOutOverlapBuffer[j*2+1] += mFFTBuffer[hFFT->BitReversed[j]+1] * mWindow[j*2+1];
}
// Output the first half of the overlap buffer, they're done -
// and then shift the next half over.
if (mOutSampleCount >= 0) { // ...but not if it's the first half-window
mOutputTrack->Append((samplePtr)mOutOverlapBuffer.get(), floatSample,
mWindowSize / 2);
}
mOutSampleCount += mWindowSize / 2;
for(size_t j = 0; j < mWindowSize / 2; j++) {
mOutOverlapBuffer[j] = mOutOverlapBuffer[j + (mWindowSize / 2)];
mOutOverlapBuffer[j + (mWindowSize / 2)] = 0.0;
}
}
bool EffectNoiseRemoval::ProcessOne(int count, WaveTrack * track,
sampleCount start, sampleCount len)
{
if (track == NULL)
return false;
StartNewTrack();
if (!mDoProfile)
mOutputTrack = track->EmptyCopy();
auto bufferSize = track->GetMaxBlockSize();
Floats buffer{ bufferSize };
bool bLoopSuccess = true;
auto samplePos = start;
while (samplePos < start + len) {
//Get a blockSize of samples (smaller than the size of the buffer)
//Adjust the block size if it is the final block in the track
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.get(), floatSample, samplePos, blockSize);
mInSampleCount += blockSize;
ProcessSamples(blockSize, buffer.get());
samplePos += blockSize;
// Update the Progress meter
if (TrackProgress(count, (samplePos - start).as_double() / len.as_double())) {
bLoopSuccess = false;
break;
}
}
FinishTrack();
if (!mDoProfile) {
// Flush the output WaveTrack (since it's buffered)
mOutputTrack->Flush();
// Take the output track and insert it in place of the original
// sample data (as operated on -- this may not match mT0/mT1)
if (bLoopSuccess) {
double t0 = mOutputTrack->LongSamplesToTime(start);
double tLen = mOutputTrack->LongSamplesToTime(len);
// Filtering effects always end up with more data than they started with. Delete this 'tail'.
mOutputTrack->HandleClear(tLen, mOutputTrack->GetEndTime(), false, false);
track->ClearAndPaste(t0, t0 + tLen, mOutputTrack.get(), true, false);
}
}
return bLoopSuccess;
}
// WDR: class implementations
//----------------------------------------------------------------------------
// NoiseRemovalDialog
//----------------------------------------------------------------------------
// WDR: event table for NoiseRemovalDialog
enum {
ID_BUTTON_GETPROFILE = 10001,
ID_BUTTON_LEAVENOISE,
ID_RADIOBUTTON_KEEPSIGNAL,
ID_RADIOBUTTON_KEEPNOISE,
ID_SENSITIVITY_SLIDER,
ID_GAIN_SLIDER,
ID_FREQ_SLIDER,
ID_TIME_SLIDER,
ID_SENSITIVITY_TEXT,
ID_GAIN_TEXT,
ID_FREQ_TEXT,
ID_TIME_TEXT,
};
#define SENSITIVITY_MIN 0 // Corresponds to -20 dB
#define SENSITIVITY_MAX 4000 // Corresponds to 20 dB
#define GAIN_MIN 0
#define GAIN_MAX 48 // Corresponds to -48 dB
#define FREQ_MIN 0
#define FREQ_MAX 100 // Corresponds to 1000 Hz
#define TIME_MIN 0
#define TIME_MAX 100 // Corresponds to 1.000 seconds
BEGIN_EVENT_TABLE(NoiseRemovalDialog,wxDialogWrapper)
EVT_BUTTON(wxID_OK, NoiseRemovalDialog::OnRemoveNoise)
EVT_BUTTON(wxID_CANCEL, NoiseRemovalDialog::OnCancel)
EVT_BUTTON(ID_EFFECT_PREVIEW, NoiseRemovalDialog::OnPreview)
EVT_BUTTON(ID_BUTTON_GETPROFILE, NoiseRemovalDialog::OnGetProfile)
EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPNOISE, NoiseRemovalDialog::OnKeepNoise)
EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPSIGNAL, NoiseRemovalDialog::OnKeepNoise)
EVT_SLIDER(ID_SENSITIVITY_SLIDER, NoiseRemovalDialog::OnSensitivitySlider)
EVT_SLIDER(ID_GAIN_SLIDER, NoiseRemovalDialog::OnGainSlider)
EVT_SLIDER(ID_FREQ_SLIDER, NoiseRemovalDialog::OnFreqSlider)
EVT_SLIDER(ID_TIME_SLIDER, NoiseRemovalDialog::OnTimeSlider)
EVT_TEXT(ID_SENSITIVITY_TEXT, NoiseRemovalDialog::OnSensitivityText)
EVT_TEXT(ID_GAIN_TEXT, NoiseRemovalDialog::OnGainText)
EVT_TEXT(ID_FREQ_TEXT, NoiseRemovalDialog::OnFreqText)
EVT_TEXT(ID_TIME_TEXT, NoiseRemovalDialog::OnTimeText)
END_EVENT_TABLE()
NoiseRemovalDialog::NoiseRemovalDialog(EffectNoiseRemoval * effect,
wxWindow *parent)
: EffectDialog( parent, XO("Noise Removal"), EffectTypeProcess)
{
m_pEffect = effect;
// NULL out the control members until the controls are created.
m_pButton_GetProfile = NULL;
m_pButton_Preview = NULL;
m_pButton_RemoveNoise = NULL;
Init();
m_pButton_Preview =
(wxButton *)wxWindow::FindWindowById(ID_EFFECT_PREVIEW, this);
m_pButton_RemoveNoise =
(wxButton *)wxWindow::FindWindowById(wxID_OK, this);
}
void NoiseRemovalDialog::OnGetProfile( wxCommandEvent & WXUNUSED(event))
{
EndModal(1);
}
void NoiseRemovalDialog::OnKeepNoise( wxCommandEvent & WXUNUSED(event))
{
mbLeaveNoise = mKeepNoise->GetValue();
}
void NoiseRemovalDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
{
// Save & restore parameters around Preview, because we didn't do OK.
bool oldDoProfile = m_pEffect->mDoProfile;
bool oldLeaveNoise = m_pEffect->mbLeaveNoise;
double oldSensitivity = m_pEffect->mSensitivity;
double oldGain = m_pEffect->mNoiseGain;
double oldFreq = m_pEffect->mFreqSmoothingHz;
double oldTime = m_pEffect->mAttackDecayTime;
TransferDataFromWindow();
m_pEffect->mDoProfile = false;
m_pEffect->mbLeaveNoise = mbLeaveNoise;
m_pEffect->mSensitivity = mSensitivity;
m_pEffect->mNoiseGain = -mGain;
m_pEffect->mFreqSmoothingHz = mFreq;
m_pEffect->mAttackDecayTime = mTime;
auto cleanup = finally( [&] {
m_pEffect->mSensitivity = oldSensitivity;
m_pEffect->mNoiseGain = oldGain;
m_pEffect->mFreqSmoothingHz = oldFreq;
m_pEffect->mAttackDecayTime = oldTime;
m_pEffect->mbLeaveNoise = oldLeaveNoise;
m_pEffect->mDoProfile = oldDoProfile;
} );
m_pEffect->Preview( false );
}
void NoiseRemovalDialog::OnRemoveNoise( wxCommandEvent & WXUNUSED(event))
{
mbLeaveNoise = mKeepNoise->GetValue();
EndModal(2);
}
void NoiseRemovalDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
{
EndModal(0);
}
void NoiseRemovalDialog::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 remove noise.\n"));
S.StartMultiColumn(3, wxEXPAND);
S.SetStretchyCol(2);
{
mGainT = S.Id(ID_GAIN_TEXT)
.Validator<wxTextValidator>(wxFILTER_NUMERIC)
.AddTextBox(XO("Noise re&duction (dB):"), wxT(""), 0);
mGainS = S.Id(ID_GAIN_SLIDER)
.Name(XO("Noise reduction"))
.Style(wxSL_HORIZONTAL)
.MinSize( { 150, -1 } )
.AddSlider( {}, 0, GAIN_MAX, GAIN_MIN);
mSensitivityT = S.Id(ID_SENSITIVITY_TEXT)
.Validator<wxTextValidator>(wxFILTER_NUMERIC)
.AddTextBox(XO("&Sensitivity (dB):"), wxT(""), 0);
mSensitivityS = S.Id(ID_SENSITIVITY_SLIDER)
.Name(XO("Sensitivity"))
.Style(wxSL_HORIZONTAL)
.MinSize( { 150, -1 } )
.AddSlider( {}, 0, SENSITIVITY_MAX, SENSITIVITY_MIN);
mFreqT = S.Id(ID_FREQ_TEXT)
.Validator<wxTextValidator>(wxFILTER_NUMERIC)
.AddTextBox(XO("Fr&equency smoothing (Hz):"), wxT(""), 0);
mFreqS = S.Id(ID_FREQ_SLIDER)
.Name(XO("Frequency smoothing"))
.Style(wxSL_HORIZONTAL)
.MinSize( { 150, -1 } )
.AddSlider( {}, 0, FREQ_MAX, FREQ_MIN);
mTimeT = S.Id(ID_TIME_TEXT)
.Validator<wxTextValidator>(wxFILTER_NUMERIC)
.AddTextBox(XO("Attac&k/decay time (secs):"), wxT(""), 0);
mTimeS = S.Id(ID_TIME_SLIDER)
.Name(XO("Attack/decay time"))
.Style(wxSL_HORIZONTAL)
.MinSize( { 150, -1 } )
.AddSlider( {}, 0, TIME_MAX, TIME_MIN);
S.AddPrompt(XO("Noise:"));
mKeepSignal = S.Id(ID_RADIOBUTTON_KEEPSIGNAL)
.AddRadioButton(XO("Re&move"));
mKeepNoise = S.Id(ID_RADIOBUTTON_KEEPNOISE)
.AddRadioButtonToGroup(XO("&Isolate"));
}
S.EndMultiColumn();
}
S.EndStatic();
}
bool NoiseRemovalDialog::TransferDataToWindow()
{
mSensitivityT->SetValue(wxString::Format(wxT("%.2f"), mSensitivity));
mGainT->SetValue(wxString::Format(wxT("%d"), (int)mGain));
mFreqT->SetValue(wxString::Format(wxT("%d"), (int)mFreq));
mTimeT->SetValue(wxString::Format(wxT("%.2f"), mTime));
mKeepNoise->SetValue(mbLeaveNoise);
mKeepSignal->SetValue(!mbLeaveNoise);
mSensitivityS->SetValue(TrapLong(mSensitivity*100.0 + (SENSITIVITY_MAX-SENSITIVITY_MIN+1)/2.0, SENSITIVITY_MIN, SENSITIVITY_MAX));
mGainS->SetValue(TrapLong(mGain, GAIN_MIN, GAIN_MAX));
mFreqS->SetValue(TrapLong(mFreq / 10, FREQ_MIN, FREQ_MAX));
mTimeS->SetValue(TrapLong(mTime * TIME_MAX + 0.5, TIME_MIN, TIME_MAX));
return true;
}
bool NoiseRemovalDialog::TransferDataFromWindow()
{
// Nothing to do here
return true;
}
void NoiseRemovalDialog::OnSensitivityText(wxCommandEvent & WXUNUSED(event))
{
mSensitivityT->GetValue().ToDouble(&mSensitivity);
mSensitivityS->SetValue(TrapLong(mSensitivity*100.0 + (SENSITIVITY_MAX-SENSITIVITY_MIN+1)/2.0, SENSITIVITY_MIN, SENSITIVITY_MAX));
}
void NoiseRemovalDialog::OnGainText(wxCommandEvent & WXUNUSED(event))
{
mGainT->GetValue().ToDouble(&mGain);
mGainS->SetValue(TrapLong(mGain, GAIN_MIN, GAIN_MAX));
}
void NoiseRemovalDialog::OnFreqText(wxCommandEvent & WXUNUSED(event))
{
mFreqT->GetValue().ToDouble(&mFreq);
mFreqS->SetValue(TrapLong(mFreq / 10, FREQ_MIN, FREQ_MAX));
}
void NoiseRemovalDialog::OnTimeText(wxCommandEvent & WXUNUSED(event))
{
mTimeT->GetValue().ToDouble(&mTime);
mTimeS->SetValue(TrapLong(mTime * TIME_MAX + 0.5, TIME_MIN, TIME_MAX));
}
void NoiseRemovalDialog::OnSensitivitySlider(wxCommandEvent & WXUNUSED(event))
{
mSensitivity = mSensitivityS->GetValue()/100.0 - 20.0;
mSensitivityT->SetValue(wxString::Format(wxT("%.2f"), mSensitivity));
}
void NoiseRemovalDialog::OnGainSlider(wxCommandEvent & WXUNUSED(event))
{
mGain = mGainS->GetValue();
mGainT->SetValue(wxString::Format(wxT("%d"), (int)mGain));
}
void NoiseRemovalDialog::OnFreqSlider(wxCommandEvent & WXUNUSED(event))
{
mFreq = mFreqS->GetValue() * 10;
mFreqT->SetValue(wxString::Format(wxT("%d"), (int)mFreq));
}
void NoiseRemovalDialog::OnTimeSlider(wxCommandEvent & WXUNUSED(event))
{
mTime = mTimeS->GetValue() / (TIME_MAX*1.0);
mTimeT->SetValue(wxString::Format(wxT("%.2f"), mTime));
}
#endif