/********************************************************************** Audacity: A Digital Audio Editor ClickRemoval.cpp Craig DeForest *******************************************************************//** \class EffectClickRemoval \brief An Effect. 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. *//****************************************************************//** \class ClickRemovalDialog \brief Dialog used with EffectClickRemoval *//*******************************************************************/ #include "../Audacity.h" #include #if defined(__WXMSW__) && !defined(__CYGWIN__) #include #define finite(x) _finite(x) #endif #include #include #include #include #include #include #include #include "ClickRemoval.h" #include "../ShuttleGui.h" #include "../Envelope.h" // #include "../FFT.h" #include "../WaveTrack.h" #include "../Prefs.h" #define MIN_THRESHOLD 0 #define MAX_THRESHOLD 900 #define MIN_CLICK_WIDTH 0 #define MAX_CLICK_WIDTH 40 EffectClickRemoval::EffectClickRemoval() { windowSize = 8192; // mThresholdLevel = 200; // mClickWidth = 20; sep=2049; Init(); } EffectClickRemoval::~EffectClickRemoval() { } bool EffectClickRemoval::Init() { mThresholdLevel = gPrefs->Read(wxT("/CsPresets/ClickThresholdLevel"), 200); if ((mThresholdLevel < MIN_THRESHOLD) || (mThresholdLevel > MAX_THRESHOLD)) { // corrupted Prefs? mThresholdLevel = 0; //Off-skip gPrefs->Write(wxT("/CsPresets/ClickThresholdLevel"), mThresholdLevel); } mClickWidth = gPrefs->Read(wxT("/CsPresets/ClickWidth"), 20); if ((mClickWidth < MIN_CLICK_WIDTH) || (mClickWidth > MAX_CLICK_WIDTH)) { // corrupted Prefs? mClickWidth = 0; //Off-skip gPrefs->Write(wxT("/CsPresets/ClickWidth"), mClickWidth); } return true; } bool EffectClickRemoval::CheckWhetherSkipEffect() { bool rc = ((mClickWidth == 0) || (mThresholdLevel == 0)); return rc; } bool EffectClickRemoval::PromptUser() { ClickRemovalDialog dlog(this, mParent); dlog.mThresh = mThresholdLevel; dlog.mWidth = mClickWidth; dlog.TransferDataToWindow(); dlog.CentreOnParent(); dlog.ShowModal(); if (dlog.GetReturnCode() == wxID_CANCEL) return false; mThresholdLevel = dlog.mThresh; mClickWidth = dlog.mWidth; gPrefs->Write(wxT("/CsPresets/ClickThresholdLevel"), mThresholdLevel); gPrefs->Write(wxT("/CsPresets/ClickWidth"), mClickWidth); return true; } bool EffectClickRemoval::TransferParameters( Shuttle & shuttle ) { shuttle.TransferInt(wxT("Threshold"),mThresholdLevel,0); shuttle.TransferInt(wxT("Width"),mClickWidth,0); return true; } bool EffectClickRemoval::Process() { this->CopyInputTracks(); // Set up mOutputTracks. bool bGoodResult = true; 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++; } this->ReplaceProcessedTracks(bGoodResult); return bGoodResult; } bool EffectClickRemoval::ProcessOne(int count, WaveTrack * track, sampleCount start, sampleCount len) { bool rc = true; sampleCount s = 0; sampleCount idealBlockLen = track->GetMaxBlockSize() * 4; if (idealBlockLen % windowSize != 0) idealBlockLen += (windowSize - (idealBlockLen % windowSize)); float *buffer = new float[idealBlockLen]; float *datawindow = new float[windowSize]; int i; 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(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)) { rc = false; break; } } delete[] buffer; delete[] datawindow; return rc; } void EffectClickRemoval::RemoveClicks(sampleCount len, float *buffer) { 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; jSetValidator(vld); S.SetStyle(wxSL_HORIZONTAL); mThreshS = S.Id(ID_THRESH_SLIDER).AddSlider(wxT(""), 0, MAX_THRESHOLD); mThreshS->SetName(_("Select threshold")); mThreshS->SetRange(MIN_THRESHOLD, MAX_THRESHOLD); #if defined(__WXGTK__) // Force a minimum size since wxGTK allows it to go to zero mThreshS->SetMinSize(wxSize(100, -1)); #endif // Click width mWidthT = S.Id(ID_WIDTH_TEXT).AddTextBox(_("Max spike width (higher is more sensitive):"), wxT(""), 10); mWidthT->SetValidator(vld); S.SetStyle(wxSL_HORIZONTAL); mWidthS = S.Id(ID_WIDTH_SLIDER).AddSlider(wxT(""), 0, MAX_CLICK_WIDTH); mWidthS->SetName(_("Max spike width")); mWidthS->SetRange(MIN_CLICK_WIDTH, MAX_CLICK_WIDTH); #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 ClickRemovalDialog::TransferDataToWindow() { mWidthS->SetValue(mWidth); mThreshS->SetValue(mThresh); mWidthT->SetValue(wxString::Format(wxT("%d"), mWidth)); mThreshT->SetValue(wxString::Format(wxT("%d"), mThresh)); return true; } bool ClickRemovalDialog::TransferDataFromWindow() { mWidth = TrapLong(mWidthS->GetValue(), MIN_CLICK_WIDTH, MAX_CLICK_WIDTH); mThresh = TrapLong(mThreshS->GetValue(), MIN_THRESHOLD, MAX_THRESHOLD); return true; } // WDR: handler implementations for ClickRemovalDialog void ClickRemovalDialog::OnWidthText(wxCommandEvent & event) { long val; mWidthT->GetValue().ToLong(&val); mWidthS->SetValue(TrapLong(val, MIN_CLICK_WIDTH, MAX_CLICK_WIDTH)); } void ClickRemovalDialog::OnThreshText(wxCommandEvent & event) { long val; mThreshT->GetValue().ToLong(&val); mThreshS->SetValue(TrapLong(val, MIN_THRESHOLD, MAX_THRESHOLD)); } void ClickRemovalDialog::OnWidthSlider(wxCommandEvent & event) { mWidthT->SetValue(wxString::Format(wxT("%d"), mWidthS->GetValue())); } void ClickRemovalDialog::OnThreshSlider(wxCommandEvent & event) { mThreshT->SetValue(wxString::Format(wxT("%d"), mThreshS->GetValue())); } void ClickRemovalDialog::OnPreview(wxCommandEvent & event) { TransferDataFromWindow(); mEffect->mThresholdLevel = mThresh; mEffect->mClickWidth = mWidth; mEffect->Preview(); } // WDR: handler implementations for NoiseRemovalDialog /* void ClickRemovalDialog::OnPreview(wxCommandEvent &event) { // Save & restore parameters around Preview, because we didn't do OK. int oldLevel = mEffect->mThresholdLevel; int oldWidth = mEffect->mClickWidth; int oldSep = mEffect->sep; mEffect->mThresholdLevel = m_pSlider->GetValue(); mEffect->mClickWidth = m_pSlider_width->GetValue(); // mEffect->sep = m_pSlider_sep->GetValue(); mEffect->Preview(); mEffect->sep = oldSep; mEffect->mClickWidth = oldWidth; mEffect->mThresholdLevel = oldLevel; } */