/********************************************************************** Audacity: A Digital Audio Editor ClickRemoval.cpp Craig DeForest *******************************************************************//** \class EffectClickRemoval \brief An Effect for removing clicks. Clicks are identified as small regions of high amplitude compared to the surrounding chunk of sound. Anything sufficiently tall compared to a large (2048 sample) window around it, and sufficiently narrow, is considered to be a click. The structure was largely stolen from Domonic Mazzoni's NoiseRemoval module, and reworked for the new effect. This file is intended to become part of Audacity. You may modify and/or distribute it under the same terms as Audacity itself. *//*******************************************************************/ #include "../Audacity.h" #include "ClickRemoval.h" #include #include #include #include #include "../Prefs.h" #include "../ShuttleGui.h" #include "../widgets/valnum.h" enum { ID_Thresh = 10000, ID_Width }; // Define keys, defaults, minimums, and maximums for the effect parameters // // Name Type Key Def Min Max Scale Param( Threshold, int, XO("Threshold"), 200, 0, 900, 1 ); Param( Width, int, XO("Width"), 20, 0, 40, 1 ); BEGIN_EVENT_TABLE(EffectClickRemoval, wxEvtHandler) EVT_SLIDER(ID_Thresh, EffectClickRemoval::OnThreshSlider) EVT_SLIDER(ID_Width, EffectClickRemoval::OnWidthSlider) EVT_TEXT(ID_Thresh, EffectClickRemoval::OnThreshText) EVT_TEXT(ID_Width, EffectClickRemoval::OnWidthText) END_EVENT_TABLE() EffectClickRemoval::EffectClickRemoval() { mThresholdLevel = DEF_Threshold; mClickWidth = DEF_Width; SetLinearEffectFlag(false); windowSize = 8192; sep = 2049; } EffectClickRemoval::~EffectClickRemoval() { } // IdentInterface implementation wxString EffectClickRemoval::GetSymbol() { return CLICKREMOVAL_PLUGIN_SYMBOL; } wxString EffectClickRemoval::GetDescription() { return XO("Click Removal is designed to remove clicks on audio tracks"); } // EffectIdentInterface implementation EffectType EffectClickRemoval::GetType() { return EffectTypeProcess; } // EffectClientInterface implementation bool EffectClickRemoval::GetAutomationParameters(EffectAutomationParameters & parms) { parms.Write(KEY_Threshold, mThresholdLevel); parms.Write(KEY_Width, mClickWidth); return true; } bool EffectClickRemoval::SetAutomationParameters(EffectAutomationParameters & parms) { ReadAndVerifyInt(Threshold); ReadAndVerifyInt(Width); mThresholdLevel = Threshold; mClickWidth = Width; return true; } // Effect implementation bool EffectClickRemoval::CheckWhetherSkipEffect() { return ((mClickWidth == 0) || (mThresholdLevel == 0)); } bool EffectClickRemoval::Startup() { wxString base = wxT("/Effects/ClickRemoval/"); // Migrate settings from 2.1.0 or before // Already migrated, so bail if (gPrefs->Exists(base + wxT("Migrated"))) { return true; } // Load the old "current" settings if (gPrefs->Exists(base)) { mThresholdLevel = gPrefs->Read(base + wxT("ClickThresholdLevel"), 200); if ((mThresholdLevel < MIN_Threshold) || (mThresholdLevel > MAX_Threshold)) { // corrupted Prefs? mThresholdLevel = 0; //Off-skip } mClickWidth = gPrefs->Read(base + wxT("ClickWidth"), 20); if ((mClickWidth < MIN_Width) || (mClickWidth > MAX_Width)) { // corrupted Prefs? mClickWidth = 0; //Off-skip } SaveUserPreset(GetCurrentSettingsGroup()); // Do not migrate again gPrefs->Write(base + wxT("Migrated"), true); gPrefs->Flush(); } return true; } bool EffectClickRemoval::Process() { this->CopyInputTracks(); // Set up mOutputTracks. bool bGoodResult = true; mbDidSomething = false; SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); WaveTrack *track = (WaveTrack *) iter.First(); int count = 0; while (track) { double trackStart = track->GetStartTime(); double trackEnd = track->GetEndTime(); double t0 = mT0 < trackStart? trackStart: mT0; double t1 = mT1 > trackEnd? trackEnd: mT1; if (t1 > t0) { sampleCount start = track->TimeToLongSamples(t0); sampleCount end = track->TimeToLongSamples(t1); sampleCount len = (sampleCount)(end - start); if (!ProcessOne(count, track, start, len)) { bGoodResult = false; break; } } track = (WaveTrack *) iter.Next(); count++; } if (bGoodResult && !mbDidSomething) // Processing successful, but ineffective. wxMessageBox( wxString::Format(_("Algorithm not effective on this audio. Nothing changed.")), GetName(), wxOK | wxICON_ERROR); this->ReplaceProcessedTracks(bGoodResult && mbDidSomething); return bGoodResult && mbDidSomething; } bool EffectClickRemoval::ProcessOne(int count, WaveTrack * track, sampleCount start, sampleCount len) { if (len <= windowSize/2) { wxMessageBox( wxString::Format(_("Selection must be larger than %d samples."), windowSize/2), GetName(), wxOK | wxICON_ERROR); return false; } sampleCount idealBlockLen = track->GetMaxBlockSize() * 4; if (idealBlockLen % windowSize != 0) idealBlockLen += (windowSize - (idealBlockLen % windowSize)); bool bResult = true; sampleCount s = 0; float *buffer = new float[idealBlockLen]; float *datawindow = new float[windowSize]; while ((s < len) && ((len - s) > windowSize/2)) { sampleCount block = idealBlockLen; if (s + block > len) block = len - s; track->Get((samplePtr) buffer, floatSample, start + s, block); for (int i=0; i < (block-windowSize/2); i += windowSize/2) { int wcopy = windowSize; if (i + wcopy > block) wcopy = block - i; int j; for(j=0; jSet((samplePtr) buffer, floatSample, start + s, block); s += block; if (TrackProgress(count, s / (double) len)) { bResult = false; break; } } delete[] buffer; delete[] datawindow; return bResult; } bool EffectClickRemoval::RemoveClicks(sampleCount len, float *buffer) { bool bResult = false; // This effect usually does nothing. int i; int j; int left = 0; float msw; int ww; int s2 = sep/2; float *ms_seq = new float[len]; float *b2 = new float[len]; for( i=0; i=1; wrc /= 2) { ww = mClickWidth/wrc; for( i=0; i= mThresholdLevel * ms_seq[i]/10) { if( left == 0 ) { left = i+s2; } } else { if(left != 0 && i-left+s2 <= ww*2) { float lv = buffer[left]; float rv = buffer[i+ww+s2]; for(j=left; j vldThresh(&mThresholdLevel); vldThresh.SetRange(MIN_Threshold, MAX_Threshold); mThreshT = S.Id(ID_Thresh).AddTextBox(_("Threshold (lower is more sensitive):"), wxT(""), 10); mThreshT->SetValidator(vldThresh); S.SetStyle(wxSL_HORIZONTAL); mThreshS = S.Id(ID_Thresh).AddSlider(wxT(""), mThresholdLevel, MAX_Threshold, MIN_Threshold); mThreshS->SetName(_("Threshold")); mThreshS->SetValidator(wxGenericValidator(&mThresholdLevel)); #if defined(__WXGTK__) // Force a minimum size since wxGTK allows it to go to zero mThreshS->SetMinSize(wxSize(100, -1)); #endif // Click width IntegerValidator vldWidth(&mClickWidth); vldWidth.SetRange(MIN_Width, MAX_Width); mWidthT = S.Id(ID_Width).AddTextBox(_("Max Spike Width (higher is more sensitive):"), wxT(""), 10); mWidthT->SetValidator(vldWidth); S.SetStyle(wxSL_HORIZONTAL); mWidthS = S.Id(ID_Width).AddSlider(wxT(""), mClickWidth, MAX_Width, MIN_Width); mWidthS->SetName(_("Max Spike Width")); mWidthS->SetValidator(wxGenericValidator(&mClickWidth)); #if defined(__WXGTK__) // Force a minimum size since wxGTK allows it to go to zero mWidthS->SetMinSize(wxSize(100, -1)); #endif } S.EndMultiColumn(); return; } bool EffectClickRemoval::TransferDataToWindow() { if (!mUIParent->TransferDataToWindow()) { return false; } return true; } bool EffectClickRemoval::TransferDataFromWindow() { if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) { return false; } return true; } void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt)) { mWidthT->GetValidator()->TransferFromWindow(); mWidthS->GetValidator()->TransferToWindow(); } void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt)) { mThreshT->GetValidator()->TransferFromWindow(); mThreshS->GetValidator()->TransferToWindow(); } void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt)) { mWidthS->GetValidator()->TransferFromWindow(); mWidthT->GetValidator()->TransferToWindow(); } void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt)) { mThreshS->GetValidator()->TransferFromWindow(); mThreshT->GetValidator()->TransferToWindow(); }