Audacity  2.2.2
ClickRemoval.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ClickRemoval.cpp
6 
7  Craig DeForest
8 
9 *******************************************************************//*******************************************************************/
26 
27 #include "../Audacity.h"
28 #include "ClickRemoval.h"
29 
30 #include <math.h>
31 
32 #include <wx/intl.h>
33 #include <wx/valgen.h>
34 
35 #include "../Prefs.h"
36 #include "../ShuttleGui.h"
37 #include "../widgets/ErrorDialog.h"
38 #include "../widgets/valnum.h"
39 
40 #include "../WaveTrack.h"
41 
42 enum
43 {
44  ID_Thresh = 10000,
46 };
47 
48 // Define keys, defaults, minimums, and maximums for the effect parameters
49 //
50 // Name Type Key Def Min Max Scale
51 Param( Threshold, int, wxT("Threshold"), 200, 0, 900, 1 );
52 Param( Width, int, wxT("Width"), 20, 0, 40, 1 );
53 
54 BEGIN_EVENT_TABLE(EffectClickRemoval, wxEvtHandler)
55  EVT_SLIDER(ID_Thresh, EffectClickRemoval::OnThreshSlider)
56  EVT_SLIDER(ID_Width, EffectClickRemoval::OnWidthSlider)
57  EVT_TEXT(ID_Thresh, EffectClickRemoval::OnThreshText)
58  EVT_TEXT(ID_Width, EffectClickRemoval::OnWidthText)
60 
62 {
63  mThresholdLevel = DEF_Threshold;
64  mClickWidth = DEF_Width;
65 
66  SetLinearEffectFlag(false);
67 
68  windowSize = 8192;
69  sep = 2049;
70 }
71 
73 {
74 }
75 
76 // IdentInterface implementation
77 
79 {
81 }
82 
84 {
85  return _("Click Removal is designed to remove clicks on audio tracks");
86 }
87 
89 {
90  return wxT("Click_Removal");
91 }
92 
93 // EffectDefinitionInterface implementation
94 
96 {
97  return EffectTypeProcess;
98 }
99 
100 // EffectClientInterface implementation
102  S.SHUTTLE_PARAM( mThresholdLevel, Threshold );
103  S.SHUTTLE_PARAM( mClickWidth, Width );
104  return true;
105 }
106 
108 {
109  parms.Write(KEY_Threshold, mThresholdLevel);
110  parms.Write(KEY_Width, mClickWidth);
111 
112  return true;
113 }
114 
116 {
117  ReadAndVerifyInt(Threshold);
118  ReadAndVerifyInt(Width);
119 
120  mThresholdLevel = Threshold;
121  mClickWidth = Width;
122 
123  return true;
124 }
125 
126 // Effect implementation
127 
129 {
130  return ((mClickWidth == 0) || (mThresholdLevel == 0));
131 }
132 
134 {
135  wxString base = wxT("/Effects/ClickRemoval/");
136 
137  // Migrate settings from 2.1.0 or before
138 
139  // Already migrated, so bail
140  if (gPrefs->Exists(base + wxT("Migrated")))
141  {
142  return true;
143  }
144 
145  // Load the old "current" settings
146  if (gPrefs->Exists(base))
147  {
148  mThresholdLevel = gPrefs->Read(base + wxT("ClickThresholdLevel"), 200);
149  if ((mThresholdLevel < MIN_Threshold) || (mThresholdLevel > MAX_Threshold))
150  { // corrupted Prefs?
151  mThresholdLevel = 0; //Off-skip
152  }
153  mClickWidth = gPrefs->Read(base + wxT("ClickWidth"), 20);
154  if ((mClickWidth < MIN_Width) || (mClickWidth > MAX_Width))
155  { // corrupted Prefs?
156  mClickWidth = 0; //Off-skip
157  }
158 
160 
161  // Do not migrate again
162  gPrefs->Write(base + wxT("Migrated"), true);
163  gPrefs->Flush();
164  }
165 
166  return true;
167 }
168 
170 {
171  this->CopyInputTracks(); // Set up mOutputTracks.
172  bool bGoodResult = true;
173  mbDidSomething = false;
174 
176  WaveTrack *track = (WaveTrack *) iter.First();
177  int count = 0;
178  while (track) {
179  double trackStart = track->GetStartTime();
180  double trackEnd = track->GetEndTime();
181  double t0 = mT0 < trackStart? trackStart: mT0;
182  double t1 = mT1 > trackEnd? trackEnd: mT1;
183 
184  if (t1 > t0) {
185  auto start = track->TimeToLongSamples(t0);
186  auto end = track->TimeToLongSamples(t1);
187  auto len = end - start;
188 
189  if (!ProcessOne(count, track, start, len))
190  {
191  bGoodResult = false;
192  break;
193  }
194  }
195 
196  track = (WaveTrack *) iter.Next();
197  count++;
198  }
199  if (bGoodResult && !mbDidSomething) // Processing successful, but ineffective.
201  _("Algorithm not effective on this audio. Nothing changed."),
202  wxOK | wxICON_ERROR);
203 
204  this->ReplaceProcessedTracks(bGoodResult && mbDidSomething);
205  return bGoodResult && mbDidSomething;
206 }
207 
209 {
210  if (len <= windowSize / 2)
211  {
213  wxString::Format(_("Selection must be larger than %d samples."),
214  windowSize / 2),
215  wxOK | wxICON_ERROR);
216  return false;
217  }
218 
219  auto idealBlockLen = track->GetMaxBlockSize() * 4;
220  if (idealBlockLen % windowSize != 0)
221  idealBlockLen += (windowSize - (idealBlockLen % windowSize));
222 
223  bool bResult = true;
224  decltype(len) s = 0;
225  Floats buffer{ idealBlockLen };
226  Floats datawindow{ windowSize };
227  while ((len - s) > windowSize / 2)
228  {
229  auto block = limitSampleBufferSize( idealBlockLen, len - s );
230 
231  track->Get((samplePtr) buffer.get(), floatSample, start + s, block);
232 
233  for (decltype(block) i = 0; i + windowSize / 2 < block; i += windowSize / 2)
234  {
235  auto wcopy = std::min( windowSize, block - i );
236 
237  for(decltype(wcopy) j = 0; j < wcopy; j++)
238  datawindow[j] = buffer[i+j];
239  for(auto j = wcopy; j < windowSize; j++)
240  datawindow[j] = 0;
241 
242  mbDidSomething |= RemoveClicks(windowSize, datawindow.get());
243 
244  for(decltype(wcopy) j = 0; j < wcopy; j++)
245  buffer[i+j] = datawindow[j];
246  }
247 
248  if (mbDidSomething) // RemoveClicks() actually did something.
249  track->Set((samplePtr) buffer.get(), floatSample, start + s, block);
250 
251  s += block;
252 
253  if (TrackProgress(count, s.as_double() /
254  len.as_double())) {
255  bResult = false;
256  break;
257  }
258  }
259 
260  return bResult;
261 }
262 
263 bool EffectClickRemoval::RemoveClicks(size_t len, float *buffer)
264 {
265  bool bResult = false; // This effect usually does nothing.
266  size_t i;
267  size_t j;
268  int left = 0;
269 
270  float msw;
271  int ww;
272  int s2 = sep/2;
273  Floats ms_seq{ len };
274  Floats b2{ len };
275 
276  for( i=0; i<len; i++)
277  b2[i] = buffer[i]*buffer[i];
278 
279  /* Shortcut for rms - multiple passes through b2, accumulating
280  * as we go.
281  */
282  for(i=0;i<len;i++)
283  ms_seq[i]=b2[i];
284 
285  for(i=1; (int)i < sep; i *= 2) {
286  for(j=0;j<len-i; j++)
287  ms_seq[j] += ms_seq[j+i];
288  }
289 
290  /* Cheat by truncating sep to next-lower power of two... */
291  sep = i;
292 
293  for( i=0; i<len-sep; i++ ) {
294  ms_seq[i] /= sep;
295  }
296  /* ww runs from about 4 to mClickWidth. wrc is the reciprocal;
297  * chosen so that integer roundoff doesn't clobber us.
298  */
299  int wrc;
300  for(wrc=mClickWidth/4; wrc>=1; wrc /= 2) {
301  ww = mClickWidth/wrc;
302 
303  for( i=0; i<len-sep; i++ ){
304  msw = 0;
305  for( j=0; (int)j<ww; j++) {
306  msw += b2[i+s2+j];
307  }
308  msw /= ww;
309 
310  if(msw >= mThresholdLevel * ms_seq[i]/10) {
311  if( left == 0 ) {
312  left = i+s2;
313  }
314  } else {
315  if(left != 0 && ((int)i-left+s2) <= ww*2) {
316  float lv = buffer[left];
317  float rv = buffer[i+ww+s2];
318  for(j=left; j<i+ww+s2; j++) {
319  bResult = true;
320  buffer[j]= (rv*(j-left) + lv*(i+ww+s2-j))/(float)(i+ww+s2-left);
321  b2[j] = buffer[j]*buffer[j];
322  }
323  left=0;
324  } else if(left != 0) {
325  left = 0;
326  }
327  }
328  }
329  }
330  return bResult;
331 }
332 
334 {
335  S.AddSpace(0, 5);
336  S.SetBorder(10);
337 
338  S.StartMultiColumn(3, wxEXPAND);
339  S.SetStretchyCol(2);
340  {
341  // Threshold
342  IntegerValidator<int> vldThresh(&mThresholdLevel);
343  vldThresh.SetRange(MIN_Threshold, MAX_Threshold);
344  mThreshT = S.Id(ID_Thresh).AddTextBox(_("Threshold (lower is more sensitive):"),
345  wxT(""),
346  10);
347  mThreshT->SetValidator(vldThresh);
348 
349  S.SetStyle(wxSL_HORIZONTAL);
350  mThreshS = S.Id(ID_Thresh).AddSlider( {}, mThresholdLevel, MAX_Threshold, MIN_Threshold);
351  mThreshS->SetName(_("Threshold"));
352  mThreshS->SetValidator(wxGenericValidator(&mThresholdLevel));
353  mThreshS->SetMinSize(wxSize(150, -1));
354 
355  // Click width
356  IntegerValidator<int> vldWidth(&mClickWidth);
357  vldWidth.SetRange(MIN_Width, MAX_Width);
358  mWidthT = S.Id(ID_Width).AddTextBox(_("Max Spike Width (higher is more sensitive):"),
359  wxT(""),
360  10);
361  mWidthT->SetValidator(vldWidth);
362 
363  S.SetStyle(wxSL_HORIZONTAL);
364  mWidthS = S.Id(ID_Width).AddSlider( {}, mClickWidth, MAX_Width, MIN_Width);
365  mWidthS->SetName(_("Max Spike Width"));
366  mWidthS->SetValidator(wxGenericValidator(&mClickWidth));
367  mWidthS->SetMinSize(wxSize(150, -1));
368  }
369  S.EndMultiColumn();
370 
371  return;
372 }
373 
375 {
376  if (!mUIParent->TransferDataToWindow())
377  {
378  return false;
379  }
380 
381  return true;
382 }
383 
385 {
386  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
387  {
388  return false;
389  }
390 
391  return true;
392 }
393 
394 void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt))
395 {
396  mWidthT->GetValidator()->TransferFromWindow();
397  mWidthS->GetValidator()->TransferToWindow();
398 }
399 
400 void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt))
401 {
402  mThreshT->GetValidator()->TransferFromWindow();
403  mThreshS->GetValidator()->TransferToWindow();
404 }
405 
406 void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt))
407 {
408  mWidthS->GetValidator()->TransferFromWindow();
409  mWidthT->GetValidator()->TransferToWindow();
410 }
411 
412 void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt))
413 {
414  mThreshS->GetValidator()->TransferFromWindow();
415  mThreshT->GetValidator()->TransferToWindow();
416 }
double mT1
Definition: Effect.h:461
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
bool DefineParams(ShuttleParams &S) override
Param(Threshold, int, wxT("Threshold"), 200, 0, 900, 1)
bool SaveUserPreset(const wxString &name) override
Definition: Effect.cpp:600
int MessageBox(const wxString &message, long style=DefaultMessageBoxStyle, const wxString &titleStr=wxString{})
Definition: Effect.cpp:2660
bool TrackProgress(int whichTrack, double frac, const wxString &=wxEmptyString)
Definition: Effect.cpp:1985
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
bool SetAutomationParameters(CommandParameters &parms) override
wxString GetCurrentSettingsGroup() override
Definition: Effect.cpp:801
void CopyInputTracks()
Definition: Effect.cpp:2036
wxSlider * mWidthS
Definition: ClickRemoval.h:82
bool TransferDataToWindow() override
void OnWidthText(wxCommandEvent &evt)
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: Effect.cpp:2162
void EndMultiColumn()
bool TransferDataFromWindow() override
virtual ~EffectClickRemoval()
wxSlider * mThreshS
Definition: ClickRemoval.h:83
double as_double() const
Definition: Types.h:88
Shuttle that deals with parameters. This is a base class with lots of virtual functions that do nothi...
Definition: Shuttle.h:60
wxTextCtrl * mWidthT
Definition: ClickRemoval.h:84
IdentInterfaceSymbol GetSymbol() override
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: Types.h:178
double GetStartTime() const override
Get the time at which the first clip in the track starts.
Definition: WaveTrack.cpp:1853
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:540
#define ReadAndVerifyInt(name)
Definition: Effect.h:797
void OnThreshSlider(wxCommandEvent &evt)
void Set(samplePtr buffer, sampleFormat format, sampleCount start, size_t len)
Definition: WaveTrack.cpp:2052
wxString GetDescription() override
An Effect for removing clicks.
Definition: ClickRemoval.h:31
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the Shuttle cla...
char * samplePtr
Definition: Types.h:203
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
ShuttleGui & Id(int id)
void SetStyle(int Style)
Definition: ShuttleGui.h:287
void OnWidthSlider(wxCommandEvent &evt)
wxString ManualPage() override
int min(int a, int b)
IdentInterfaceSymbol pairs a persistent string identifier used internally with an optional...
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:1625
bool GetAutomationParameters(CommandParameters &parms) override
bool ProcessOne(int count, WaveTrack *track, sampleCount start, sampleCount len)
void OnThreshText(wxCommandEvent &evt)
wxWindow * mUIParent
Definition: Effect.h:472
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
wxTextCtrl * mThreshT
Definition: ClickRemoval.h:85
bool Process() override
EffectType
EffectType GetType() override
bool RemoveClicks(size_t len, float *buffer)
END_EVENT_TABLE()
wxSizerItem * AddSpace(int width, int height)
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumCopied=nullptr) const
Definition: WaveTrack.cpp:1971
void PopulateOrExchange(ShuttleGui &S) override
void SetBorder(int Border)
Definition: ShuttleGui.h:286
std::shared_ptr< TrackList > mOutputTracks
Definition: Effect.h:459
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:203
double mT0
Definition: Effect.h:460
bool Startup() override
bool CheckWhetherSkipEffect() override
#define CLICKREMOVAL_PLUGIN_SYMBOL
Definition: ClickRemoval.h:29
wxSlider * AddSlider(const wxString &Prompt, int pos, int Max, int Min=0)
Definition: ShuttleGui.cpp:497