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 // EffectIdentInterface implementation
94 
96 {
97  return EffectTypeProcess;
98 }
99 
100 // EffectClientInterface implementation
101 
102 bool EffectClickRemoval::GetAutomationParameters(EffectAutomationParameters & parms)
103 {
104  parms.Write(KEY_Threshold, mThresholdLevel);
105  parms.Write(KEY_Width, mClickWidth);
106 
107  return true;
108 }
109 
110 bool EffectClickRemoval::SetAutomationParameters(EffectAutomationParameters & parms)
111 {
112  ReadAndVerifyInt(Threshold);
113  ReadAndVerifyInt(Width);
114 
115  mThresholdLevel = Threshold;
116  mClickWidth = Width;
117 
118  return true;
119 }
120 
121 // Effect implementation
122 
124 {
125  return ((mClickWidth == 0) || (mThresholdLevel == 0));
126 }
127 
129 {
130  wxString base = wxT("/Effects/ClickRemoval/");
131 
132  // Migrate settings from 2.1.0 or before
133 
134  // Already migrated, so bail
135  if (gPrefs->Exists(base + wxT("Migrated")))
136  {
137  return true;
138  }
139 
140  // Load the old "current" settings
141  if (gPrefs->Exists(base))
142  {
143  mThresholdLevel = gPrefs->Read(base + wxT("ClickThresholdLevel"), 200);
144  if ((mThresholdLevel < MIN_Threshold) || (mThresholdLevel > MAX_Threshold))
145  { // corrupted Prefs?
146  mThresholdLevel = 0; //Off-skip
147  }
148  mClickWidth = gPrefs->Read(base + wxT("ClickWidth"), 20);
149  if ((mClickWidth < MIN_Width) || (mClickWidth > MAX_Width))
150  { // corrupted Prefs?
151  mClickWidth = 0; //Off-skip
152  }
153 
155 
156  // Do not migrate again
157  gPrefs->Write(base + wxT("Migrated"), true);
158  gPrefs->Flush();
159  }
160 
161  return true;
162 }
163 
165 {
166  this->CopyInputTracks(); // Set up mOutputTracks.
167  bool bGoodResult = true;
168  mbDidSomething = false;
169 
171  WaveTrack *track = (WaveTrack *) iter.First();
172  int count = 0;
173  while (track) {
174  double trackStart = track->GetStartTime();
175  double trackEnd = track->GetEndTime();
176  double t0 = mT0 < trackStart? trackStart: mT0;
177  double t1 = mT1 > trackEnd? trackEnd: mT1;
178 
179  if (t1 > t0) {
180  auto start = track->TimeToLongSamples(t0);
181  auto end = track->TimeToLongSamples(t1);
182  auto len = end - start;
183 
184  if (!ProcessOne(count, track, start, len))
185  {
186  bGoodResult = false;
187  break;
188  }
189  }
190 
191  track = (WaveTrack *) iter.Next();
192  count++;
193  }
194  if (bGoodResult && !mbDidSomething) // Processing successful, but ineffective.
196  _("Algorithm not effective on this audio. Nothing changed."),
197  wxOK | wxICON_ERROR);
198 
199  this->ReplaceProcessedTracks(bGoodResult && mbDidSomething);
200  return bGoodResult && mbDidSomething;
201 }
202 
203 bool EffectClickRemoval::ProcessOne(int count, WaveTrack * track, sampleCount start, sampleCount len)
204 {
205  if (len <= windowSize / 2)
206  {
208  wxString::Format(_("Selection must be larger than %d samples."),
209  windowSize / 2),
210  wxOK | wxICON_ERROR);
211  return false;
212  }
213 
214  auto idealBlockLen = track->GetMaxBlockSize() * 4;
215  if (idealBlockLen % windowSize != 0)
216  idealBlockLen += (windowSize - (idealBlockLen % windowSize));
217 
218  bool bResult = true;
219  decltype(len) s = 0;
220  Floats buffer{ idealBlockLen };
221  Floats datawindow{ windowSize };
222  while ((len - s) > windowSize / 2)
223  {
224  auto block = limitSampleBufferSize( idealBlockLen, len - s );
225 
226  track->Get((samplePtr) buffer.get(), floatSample, start + s, block);
227 
228  for (decltype(block) i = 0; i + windowSize / 2 < block; i += windowSize / 2)
229  {
230  auto wcopy = std::min( windowSize, block - i );
231 
232  for(decltype(wcopy) j = 0; j < wcopy; j++)
233  datawindow[j] = buffer[i+j];
234  for(auto j = wcopy; j < windowSize; j++)
235  datawindow[j] = 0;
236 
237  mbDidSomething |= RemoveClicks(windowSize, datawindow.get());
238 
239  for(decltype(wcopy) j = 0; j < wcopy; j++)
240  buffer[i+j] = datawindow[j];
241  }
242 
243  if (mbDidSomething) // RemoveClicks() actually did something.
244  track->Set((samplePtr) buffer.get(), floatSample, start + s, block);
245 
246  s += block;
247 
248  if (TrackProgress(count, s.as_double() /
249  len.as_double())) {
250  bResult = false;
251  break;
252  }
253  }
254 
255  return bResult;
256 }
257 
258 bool EffectClickRemoval::RemoveClicks(size_t len, float *buffer)
259 {
260  bool bResult = false; // This effect usually does nothing.
261  size_t i;
262  size_t j;
263  int left = 0;
264 
265  float msw;
266  int ww;
267  int s2 = sep/2;
268  Floats ms_seq{ len };
269  Floats b2{ len };
270 
271  for( i=0; i<len; i++)
272  b2[i] = buffer[i]*buffer[i];
273 
274  /* Shortcut for rms - multiple passes through b2, accumulating
275  * as we go.
276  */
277  for(i=0;i<len;i++)
278  ms_seq[i]=b2[i];
279 
280  for(i=1; (int)i < sep; i *= 2) {
281  for(j=0;j<len-i; j++)
282  ms_seq[j] += ms_seq[j+i];
283  }
284 
285  /* Cheat by truncating sep to next-lower power of two... */
286  sep = i;
287 
288  for( i=0; i<len-sep; i++ ) {
289  ms_seq[i] /= sep;
290  }
291  /* ww runs from about 4 to mClickWidth. wrc is the reciprocal;
292  * chosen so that integer roundoff doesn't clobber us.
293  */
294  int wrc;
295  for(wrc=mClickWidth/4; wrc>=1; wrc /= 2) {
296  ww = mClickWidth/wrc;
297 
298  for( i=0; i<len-sep; i++ ){
299  msw = 0;
300  for( j=0; (int)j<ww; j++) {
301  msw += b2[i+s2+j];
302  }
303  msw /= ww;
304 
305  if(msw >= mThresholdLevel * ms_seq[i]/10) {
306  if( left == 0 ) {
307  left = i+s2;
308  }
309  } else {
310  if(left != 0 && ((int)i-left+s2) <= ww*2) {
311  float lv = buffer[left];
312  float rv = buffer[i+ww+s2];
313  for(j=left; j<i+ww+s2; j++) {
314  bResult = true;
315  buffer[j]= (rv*(j-left) + lv*(i+ww+s2-j))/(float)(i+ww+s2-left);
316  b2[j] = buffer[j]*buffer[j];
317  }
318  left=0;
319  } else if(left != 0) {
320  left = 0;
321  }
322  }
323  }
324  }
325  return bResult;
326 }
327 
329 {
330  S.AddSpace(0, 5);
331  S.SetBorder(10);
332 
333  S.StartMultiColumn(3, wxEXPAND);
334  S.SetStretchyCol(2);
335  {
336  // Threshold
337  IntegerValidator<int> vldThresh(&mThresholdLevel);
338  vldThresh.SetRange(MIN_Threshold, MAX_Threshold);
339  mThreshT = S.Id(ID_Thresh).AddTextBox(_("Threshold (lower is more sensitive):"),
340  wxT(""),
341  10);
342  mThreshT->SetValidator(vldThresh);
343 
344  S.SetStyle(wxSL_HORIZONTAL);
345  mThreshS = S.Id(ID_Thresh).AddSlider( {}, mThresholdLevel, MAX_Threshold, MIN_Threshold);
346  mThreshS->SetName(_("Threshold"));
347  mThreshS->SetValidator(wxGenericValidator(&mThresholdLevel));
348  mThreshS->SetMinSize(wxSize(150, -1));
349 
350  // Click width
351  IntegerValidator<int> vldWidth(&mClickWidth);
352  vldWidth.SetRange(MIN_Width, MAX_Width);
353  mWidthT = S.Id(ID_Width).AddTextBox(_("Max Spike Width (higher is more sensitive):"),
354  wxT(""),
355  10);
356  mWidthT->SetValidator(vldWidth);
357 
358  S.SetStyle(wxSL_HORIZONTAL);
359  mWidthS = S.Id(ID_Width).AddSlider( {}, mClickWidth, MAX_Width, MIN_Width);
360  mWidthS->SetName(_("Max Spike Width"));
361  mWidthS->SetValidator(wxGenericValidator(&mClickWidth));
362  mWidthS->SetMinSize(wxSize(150, -1));
363  }
364  S.EndMultiColumn();
365 
366  return;
367 }
368 
370 {
371  if (!mUIParent->TransferDataToWindow())
372  {
373  return false;
374  }
375 
376  return true;
377 }
378 
380 {
381  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
382  {
383  return false;
384  }
385 
386  return true;
387 }
388 
389 void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt))
390 {
391  mWidthT->GetValidator()->TransferFromWindow();
392  mWidthS->GetValidator()->TransferToWindow();
393 }
394 
395 void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt))
396 {
397  mThreshT->GetValidator()->TransferFromWindow();
398  mThreshS->GetValidator()->TransferToWindow();
399 }
400 
401 void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt))
402 {
403  mWidthS->GetValidator()->TransferFromWindow();
404  mWidthT->GetValidator()->TransferToWindow();
405 }
406 
407 void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt))
408 {
409  mThreshS->GetValidator()->TransferFromWindow();
410  mThreshT->GetValidator()->TransferToWindow();
411 }
double mT1
Definition: Effect.h:460
Param(Threshold, int, wxT("Threshold"), 200, 0, 900, 1)
bool SaveUserPreset(const wxString &name) override
Definition: Effect.cpp:615
int MessageBox(const wxString &message, long style=DefaultMessageBoxStyle, const wxString &titleStr=wxString{})
Definition: Effect.cpp:2655
bool TrackProgress(int whichTrack, double frac, const wxString &=wxEmptyString)
Definition: Effect.cpp:1978
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true) const
Definition: WaveTrack.cpp:1950
wxString GetCurrentSettingsGroup() override
Definition: Effect.cpp:814
void CopyInputTracks()
Definition: Effect.cpp:2029
bool SetAutomationParameters(EffectAutomationParameters &parms) override
wxSlider * mWidthS
Definition: ClickRemoval.h:81
bool TransferDataToWindow() override
void OnWidthText(wxCommandEvent &evt)
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: Effect.cpp:2160
void EndMultiColumn()
bool TransferDataFromWindow() override
virtual ~EffectClickRemoval()
wxSlider * mThreshS
Definition: ClickRemoval.h:82
wxTextCtrl * mWidthT
Definition: ClickRemoval.h:83
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:493
#define ReadAndVerifyInt(name)
Definition: Effect.h:786
void OnThreshSlider(wxCommandEvent &evt)
void Set(samplePtr buffer, sampleFormat format, sampleCount start, size_t len)
Definition: WaveTrack.cpp:2027
wxString GetDescription() override
An Effect for removing clicks.
Definition: ClickRemoval.h:31
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
ShuttleGui & Id(int id)
void SetStyle(int Style)
Definition: ShuttleGui.h:252
void OnWidthSlider(wxCommandEvent &evt)
wxString ManualPage() override
int min(int a, int b)
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:1604
bool ProcessOne(int count, WaveTrack *track, sampleCount start, sampleCount len)
void OnThreshText(wxCommandEvent &evt)
wxWindow * mUIParent
Definition: Effect.h:471
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
wxTextCtrl * mThreshT
Definition: ClickRemoval.h:84
bool Process() override
EffectType GetType() override
bool RemoveClicks(size_t len, float *buffer)
END_EVENT_TABLE()
wxSizerItem * AddSpace(int width, int height)
wxString GetSymbol() override
void PopulateOrExchange(ShuttleGui &S) override
void SetBorder(int Border)
Definition: ShuttleGui.h:251
std::shared_ptr< TrackList > mOutputTracks
Definition: Effect.h:458
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:192
double mT0
Definition: Effect.h:459
bool GetAutomationParameters(EffectAutomationParameters &parms) override
double GetStartTime() const
Get the time at which the first clip in the track starts.
Definition: WaveTrack.cpp:1832
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:456