/********************************************************************** 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 Hanning 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 "../Experimental.h" #include "NoiseReduction.h" #include "../Prefs.h" #include #include #include #if defined(__WXMSW__) && !defined(__CYGWIN__) #include #define finite(x) _finite(x) #endif #include #include #include #include #include #include #include #include // 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 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 wxChar *name; } discriminationMethodInfo[DM_N_METHODS] = { { _("Median") }, { _("Second greatest") }, { _("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 wxChar *name; int 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. { _("none, Hann (2.0.6 behavior)"), 2, { 1, 0, 0 }, { 0.5, -0.5, 0 }, 0.5 }, { _("Hann, none"), 2, { 0.5, -0.5, 0 }, { 1, 0, 0 }, 0.5 }, { _("Hann, Hann (default)"), 4, { 0.5, -0.5, 0 }, { 0.5, -0.5, 0 }, 0.375 }, { _("Blackman, Hann"), 4, { 0.42, -0.5, 0.08 }, { 0.5, -0.5, 0 }, 0.335 }, { _("Hamming, none"), 2, { 0.54, -0.46, 0.0 }, { 1, 0, 0 }, 0.54 }, { _("Hamming, Hann"), 4, { 0.54, -0.46, 0.0 }, { 0.5, -0.5, 0 }, 0.385 }, { _("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(int 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 int 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() const; int WindowSize() const { return 1 << (3 + mWindowSizeChoice); } int StepsPerWindow() const { return 1 << (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, SelectedTrackListOfKindIterator &iter, 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, sampleCount 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 int mWindowSize; // These have that size: HFFT hFFT; FloatVector mFFTBuffer; FloatVector mInWaveBuffer; FloatVector mOutOverlapBuffer; // These have that size, or 0: FloatVector mInWindow; FloatVector mOutWindow; const int mSpectrumSize; FloatVector mFreqSmoothingScratch; const int mFreqSmoothingBins; // When spectral selection limits the affected band: int mBinLow; // inclusive lower bound int mBinHigh; // exclusive upper bound const int mNoiseReductionChoice; const int mStepsPerWindow; const int mStepSize; const int mMethod; const double mNewSensitivity; sampleCount mInSampleCount; sampleCount mOutStepCount; int mInWavePos; float mOneBlockAttack; float mOneBlockRelease; float mNoiseAttenFactor; float mOldSensitivityFactor; int mNWindowsToExamine; int mCenter; int mHistoryLen; struct Record { Record(int spectrumSize) : mSpectrums(spectrumSize) , mGains(spectrumSize) , mRealFFTs(spectrumSize - 1) , mImagFFTs(spectrumSize - 1) { } FloatVector mSpectrums; FloatVector mGains; FloatVector mRealFFTs; FloatVector mImagFFTs; }; std::vector mQueue; }; /****************************************************************//** \class EffectNoiseReduction::Dialog \brief Dialog used with EffectNoiseReduction **//*****************************************************************/ //---------------------------------------------------------------------------- // EffectNoiseReduction::Dialog //---------------------------------------------------------------------------- class EffectNoiseReduction::Dialog: public EffectDialog { public: // constructors and destructors Dialog (EffectNoiseReduction *effect, Settings *settings, wxWindow *parent, bool bHasProfile, bool bAllowTwiddleSettings); void PopulateOrExchange(ShuttleGui & S); bool TransferDataToWindow(); bool TransferDataFromWindow(); 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); void OnReduceNoise( wxCommandEvent &event ); void OnCancel( 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(new EffectNoiseReduction::Settings) { Init(); } EffectNoiseReduction::~EffectNoiseReduction() { } // IdentInterface implementation wxString EffectNoiseReduction::GetSymbol() { return NOISEREDUCTION_PLUGIN_SYMBOL; } wxString EffectNoiseReduction::GetDescription() { return XO("Removes background noise such as fans, tape noise, or hums"); } // EffectIdentInterface implementation EffectType EffectNoiseReduction::GetType() { return EffectTypeProcess; } bool EffectNoiseReduction::Init() { return true; } bool EffectNoiseReduction::CheckWhetherSkipEffect() { return false; } bool EffectNoiseReduction::PromptUser(wxWindow *parent) { // 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, (mStatistics.get() != 0), (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 struct PrefsTableEntry { typedef FieldType (StructureType::*MemberPointer); MemberPointer field; const wxChar *name; FieldType defaultValue; }; template void readPrefs( StructureType *structure, const wxString &prefix, const PrefsTableEntry *fields, int numFields) { for (int ii = 0; ii < numFields; ++ii) { const PrefsTableEntry &entry = fields[ii]; gPrefs->Read(prefix + entry.name, &(structure->*(entry.field)), entry.defaultValue); } } template void writePrefs( StructureType *structure, const wxString &prefix, const PrefsTableEntry *fields, int numFields) { for (int ii = 0; ii < numFields; ++ii) { const PrefsTableEntry &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 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"), 0.0 }, // Advanced settings { &Settings::mOldSensitivity, wxT("OldSensitivity"), DEFAULT_OLD_SENSITIVITY }, }; static int doubleTableSize = sizeof(doubleTable) / sizeof(doubleTable[0]); static const PrefsTableEntry 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() const { if (StepsPerWindow() < windowTypesInfo[mWindowTypes].minSteps) { ::wxMessageBox(_("Steps per block are too few for the window types.")); return false; } if (StepsPerWindow() > WindowSize()) { ::wxMessageBox(_("Steps per block cannot exceed the window size.")); return false; } if (mMethod == DM_MEDIAN && StepsPerWindow() > 4) { ::wxMessageBox(_("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. SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); WaveTrack *track = (WaveTrack *) iter.First(); if (!track) return false; // Initialize statistics if gathering them, or check for mismatched (advanced) // settings if reducing noise. if (mSettings->mDoProfile) { int spectrumSize = 1 + mSettings->WindowSize() / 2; mStatistics.reset (new Statistics(spectrumSize, track->GetRate(), mSettings->mWindowTypes)); } else if (mStatistics->mWindowSize != mSettings->WindowSize()) { // possible only with advanced settings ::wxMessageBox(_("You must specify the same window size for steps 1 and 2.")); return false; } else if (mStatistics->mWindowTypes != mSettings->mWindowTypes) { // A warning only ::wxMessageBox(_("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, iter, 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() { EndFFT(hFFT); for(int ii = 0, nn = mQueue.size(); ii < nn; ++ii) delete mQueue[ii]; } bool EffectNoiseReduction::Worker::Process (EffectNoiseReduction &effect, Statistics &statistics, TrackFactory &factory, SelectedTrackListOfKindIterator &iter, double mT0, double mT1) { int count = 0; WaveTrack *track = (WaveTrack *) iter.First(); while (track) { if (track->GetRate() != mSampleRate) { if (mDoProfile) ::wxMessageBox(_("All noise profile data must have the same sample rate.")); else ::wxMessageBox(_("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, mT0); double t1 = std::min(trackEnd, mT1); if (t1 > t0) { sampleCount start = track->TimeToLongSamples(t0); sampleCount end = track->TimeToLongSamples(t1); sampleCount len = (sampleCount)(end - start); if (!ProcessOne(effect, statistics, factory, count, track, start, len)) return false; } track = (WaveTrack *) iter.Next(); ++count; } if (mDoProfile) { if (statistics.mTotalWindows == 0) { ::wxMessageBox(_("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 (int ii = 0; ii < mSpectrumSize; ++ii) gains[ii] = log(gains[ii]); for (int ii = 0; ii < mSpectrumSize; ++ii) { const int j0 = std::max(0, ii - 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 (int 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(InitializeFFT(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 int nAttackBlocks = 1 + (int)(settings.mAttackTime * sampleRate / mStepSize); const int nReleaseBlocks = 1 + (int)(settings.mReleaseTime * sampleRate / mStepSize); // Applies to amplitudes, divide by 20: mNoiseAttenFactor = pow(10.0, noiseGain / 20.0); // Apply to gain factors which apply to amplitudes, divide by 20: mOneBlockAttack = pow(10.0, (noiseGain / (20.0 * nAttackBlocks))); mOneBlockRelease = pow(10.0, (noiseGain / (20.0 * 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 (int ii = 0; ii < mHistoryLen; ++ii) mQueue[ii] = new 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 (int 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 (int 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 (int 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(int 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 = -(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 = -(mHistoryLen - 1) // ... and then must pass over the padded windows, // before the first full window: - (mStepsPerWindow - 1); } mInSampleCount = 0; } void EffectNoiseReduction::Worker::ProcessSamples (Statistics &statistics, WaveTrack *outputTrack, sampleCount len, float *buffer) { while (len && mOutStepCount * mStepSize < mInSampleCount) { int avail = std::min(int(len), mWindowSize - mInWavePos); memmove(&mInWaveBuffer[mInWavePos], buffer, avail * sizeof(float)); buffer += avail; len -= avail; mInWavePos += avail; if (mInWavePos == 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 (int ii = 0; ii < mWindowSize; ++ii) mFFTBuffer[ii] = mInWaveBuffer[ii] * mInWindow[ii]; else memmove(&mFFTBuffer[0], &mInWaveBuffer[0], mWindowSize * sizeof(float)); RealFFTf(&mFFTBuffer[0], hFFT); 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 int last = mSpectrumSize - 1; for (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() { Record *save = mQueue[mHistoryLen - 1]; mQueue.pop_back(); mQueue.insert(mQueue.begin(), save); } 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 (int 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. int 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 (int 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 (int 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 (int 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 (int 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 (int jj = 0; jj < mSpectrumSize; ++jj) { for (int 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 >= -(mStepsPerWindow - 1)) { Record &record = *mQueue[mHistoryLen - 1]; // end of the queue const int 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]; int 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); // Overlap-add if (mOutWindow.size() > 0) { float *pOut = &mOutOverlapBuffer[0]; float *pWindow = &mOutWindow[0]; int *pBitReversed = &hFFT->BitReversed[0]; for (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 (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(); std::auto_ptr outputTrack( mDoProfile ? NULL : factory.NewWaveTrack(track->GetSampleFormat(), track->GetRate())); sampleCount bufferSize = track->GetMaxBlockSize(); FloatVector buffer(bufferSize); bool bLoopSuccess = true; sampleCount blockSize; sampleCount samplePos = start; while (bLoopSuccess && samplePos < start + len) { //Get a blockSize of samples (smaller than the size of the buffer) blockSize = std::min(start + len - samplePos, track->GetBestBlockSize(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) / (double)len); } 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); bool bResult = track->ClearAndPaste(t0, t0 + tLen, &*outputTrack, true, false); wxASSERT(bResult); // TO DO: Actually handle this. } 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, wxTextValidator &vld, ShuttleGui &S) const { wxTextCtrl *const text = S.Id(id + 1).AddTextBox(textBoxCaption(), wxT(""), 0); S.SetStyle(wxSL_HORIZONTAL); text->SetValidator(vld); wxSlider *const slider = S.Id(id).AddSlider(wxT(""), 0, sliderMax); slider->SetName(sliderName()); slider->SetRange(0, sliderMax); slider->SetSizeHints(150, -1); } MemberPointer field; double valueMin; double valueMax; long sliderMax; // (valueMin - valueMax) / sliderMax is the value increment of the slider const wxChar* format; bool formatAsInt; const wxString textBoxCaption_; wxString textBoxCaption() const { return wxGetTranslation(textBoxCaption_); } const wxString sliderName_; wxString sliderName() const { return wxGetTranslation(sliderName_); } ControlInfo(MemberPointer f, double vMin, double vMax, long sMax, const wxChar* fmt, bool fAsInt, const wxString &caption, const wxString &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, 6, 6, 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, wxDialog) 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_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, _("Noise Reduction"), EffectTypeProcess) , 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(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)) { 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. EffectNoiseReduction::Settings oldSettings(*m_pSettings); *m_pSettings = mTempSettings; m_pSettings->mDoProfile = false; m_pEffect->Preview(); *m_pSettings = oldSettings; } void EffectNoiseReduction::Dialog::OnReduceNoise( wxCommandEvent & WXUNUSED(event)) { if (!TransferDataFromWindow()) return; EndModal(2); } void EffectNoiseReduction::Dialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { EndModal(0); } void EffectNoiseReduction::Dialog::PopulateOrExchange(ShuttleGui & S) { S.StartStatic(_("Step 1")); { S.AddVariableText(_( "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(_("&Get Noise Profile")); } S.EndStatic(); S.StartStatic(_("Step 2")); { S.AddVariableText(_( "Select all of the audio you want filtered, choose how much noise you want\nfiltered out, and then click 'OK' to reduce noise.\n")); #if defined(__WXGTK__) // Put everything inside a panel to workaround a problem on Linux where the access key // does not work if it is defined within static text on the first control. S.SetStyle(wxTAB_TRAVERSAL); S.StartPanel(); #endif S.StartMultiColumn(3, wxEXPAND); S.SetStretchyCol(2); { wxTextValidator vld(wxFILTER_NUMERIC); for (int id = FIRST_SLIDER; id < END_OF_BASIC_SLIDERS; id += 2) { const ControlInfo &info = controlInfo()[(id - FIRST_SLIDER) / 2]; info.CreateControls(id, vld, S); } } S.EndMultiColumn(); S.StartMultiColumn( 2 #ifdef RESIDUE_CHOICE +1 #endif #ifdef ISOLATE_CHOICE +1 #endif , wxALIGN_CENTER_HORIZONTAL); { S.AddPrompt(_("Noise:")); mKeepSignal = S.Id(ID_RADIOBUTTON_KEEPSIGNAL) .AddRadioButton(_("Re&duce")); /* i18n-hint: Translate differently from "Residue" ! */ #ifdef ISOLATE_CHOICE mKeepNoise = S.Id(ID_RADIOBUTTON_KEEPNOISE) .AddRadioButtonToGroup(_("&Isolate")); #endif #ifdef RESIDUE_CHOICE mResidue = S.Id(ID_RADIOBUTTON_RESIDUE) .AddRadioButtonToGroup(_("Resid&ue")); /* i18n-hint: Means the difference between effect and original sound. Translate differently from "Reduce" ! */ #endif } S.EndMultiColumn(); #if defined(__WXGTK__) S.EndPanel(); #endif } S.EndStatic(); #ifdef ADVANCED_SETTINGS S.StartStatic(_("Advanced Settings")); { #if defined(__WXGTK__) // Put everything inside a panel to workaround a problem on Linux where the access key // does not work if it is defined within static text on the first control. S.SetStyle(wxTAB_TRAVERSAL); S.StartPanel(); #endif S.StartMultiColumn(2); { { wxArrayString windowTypeChoices; for (int ii = 0; ii < WT_N_WINDOW_TYPES; ++ii) windowTypeChoices.Add(windowTypesInfo[ii].name); S.TieChoice(_("&Window types") + wxString(wxT(":")), mTempSettings.mWindowTypes, &windowTypeChoices); } { wxArrayString windowSizeChoices; windowSizeChoices.Add(_("8")); windowSizeChoices.Add(_("16")); windowSizeChoices.Add(_("32")); windowSizeChoices.Add(_("64")); windowSizeChoices.Add(_("128")); windowSizeChoices.Add(_("256")); windowSizeChoices.Add(_("512")); windowSizeChoices.Add(_("1024")); windowSizeChoices.Add(_("2048 (default)")); windowSizeChoices.Add(_("4096")); windowSizeChoices.Add(_("8192")); windowSizeChoices.Add(_("16384")); S.TieChoice(_("Window si&ze") + wxString(wxT(":")), mTempSettings.mWindowSizeChoice, &windowSizeChoices); } { wxArrayString stepsPerWindowChoices; stepsPerWindowChoices.Add(_("2")); stepsPerWindowChoices.Add(_("4 (default)")); stepsPerWindowChoices.Add(_("8")); stepsPerWindowChoices.Add(_("16")); stepsPerWindowChoices.Add(_("32")); stepsPerWindowChoices.Add(_("64")); S.TieChoice(_("S&teps per window") + wxString(wxT(":")), mTempSettings.mStepsPerWindowChoice, &stepsPerWindowChoices); } S.Id(ID_CHOICE_METHOD); { wxArrayString methodChoices; int nn = DM_N_METHODS; #ifndef OLD_METHOD_AVAILABLE --nn; #endif for (int ii = 0; ii < nn; ++ii) methodChoices.Add(discriminationMethodInfo[ii].name); S.TieChoice(_("Discrimination &method") + wxString(wxT(":")), mTempSettings.mMethod, &methodChoices); } } S.EndMultiColumn(); S.StartMultiColumn(3, wxEXPAND); S.SetStretchyCol(2); { wxTextValidator vld(wxFILTER_NUMERIC); 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, vld, S); } } S.EndMultiColumn(); #if defined(__WXGTK__) S.EndPanel(); #endif } 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(wxWindow::FindWindowById(id, this)); wxTextCtrl* text = static_cast(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() { // Do the choice controls: if (!EffectDialog::TransferDataFromWindow()) return false; wxCommandEvent dummy; OnNoiseReductionChoice(dummy); return mTempSettings.Validate(); } 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(wxWindow::FindWindowById(id, this)); wxSlider* slider = static_cast(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(wxWindow::FindWindowById(id, this)); wxTextCtrl* text = static_cast(wxWindow::FindWindowById(id + 1, this)); double &field = mTempSettings.*(info.field); field = info.Value(slider->GetValue()); text->SetValue(info.Text(field)); }