Audacity 3.2.0
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#include "ClickRemoval.h"
27#include "EffectEditor.h"
28#include "EffectOutputTracks.h"
29#include "LoadEffects.h"
30
31#include <math.h>
32
33#include <wx/slider.h>
34#include <wx/valgen.h>
35
36#include "Prefs.h"
37#include "ShuttleGui.h"
38#include "AudacityMessageBox.h"
39#include "../widgets/valnum.h"
40
41#include "WaveTrack.h"
42
43enum
44{
45 ID_Thresh = 10000,
47};
48
50{
53 > parameters;
54 return parameters;
55}
56
58{ XO("Click Removal") };
59
61
62BEGIN_EVENT_TABLE(EffectClickRemoval, wxEvtHandler)
68
70{
71 Parameters().Reset(*this);
72
73 SetLinearEffectFlag(false);
74
75 windowSize = 8192;
76 sep = 2049;
77}
78
80{
81}
82
83// ComponentInterface implementation
84
86{
87 return Symbol;
88}
89
91{
92 return XO("Click Removal is designed to remove clicks on audio tracks");
93}
94
96{
97 return L"Click_Removal";
98}
99
100// EffectDefinitionInterface implementation
101
103{
104 return EffectTypeProcess;
105}
106
107// Effect implementation
108
110{
111 return ((mClickWidth == 0) || (mThresholdLevel == 0));
112}
113
115{
116 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
117 bool bGoodResult = true;
118 mbDidSomething = false;
119
120 int count = 0;
121 for (auto track : outputs.Get().Selected<WaveTrack>()) {
122 double trackStart = track->GetStartTime();
123 double trackEnd = track->GetEndTime();
124 double t0 = std::max(mT0, trackStart);
125 double t1 = std::min(trackEnd, mT1);
126
127 if (t1 > t0) {
128 auto start = track->TimeToLongSamples(t0);
129 auto end = track->TimeToLongSamples(t1);
130 auto len = end - start;
131 for (const auto pChannel : track->Channels())
132 if (!ProcessOne(count++, *pChannel, start, len)) {
133 bGoodResult = false;
134 goto done;
135 }
136 }
137 }
138 done:
139 if (bGoodResult && !mbDidSomething) // Processing successful, but ineffective.
141 XO("Algorithm not effective on this audio. Nothing changed."),
142 wxOK | wxICON_ERROR );
143
144 if (bGoodResult && mbDidSomething)
145 outputs.Commit();
146
147 return bGoodResult && mbDidSomething;
148}
149
151 int count, WaveChannel &track, sampleCount start, sampleCount len)
152{
153 if (len <= windowSize / 2) {
155 XO("Selection must be larger than %d samples.")
156 .Format(windowSize / 2),
157 wxOK | wxICON_ERROR );
158 return false;
159 }
160
161 auto idealBlockLen = track.GetMaxBlockSize() * 4;
162 if (idealBlockLen % windowSize != 0)
163 idealBlockLen += (windowSize - (idealBlockLen % windowSize));
164
165 bool bResult = true;
166 decltype(len) s = 0;
167 Floats buffer{ idealBlockLen };
168 Floats datawindow{ windowSize };
169 while ((len - s) > windowSize / 2) {
170 auto block = limitSampleBufferSize(idealBlockLen, len - s);
171 track.GetFloats(buffer.get(), start + s, block);
172 for (decltype(block) i = 0;
173 i + windowSize / 2 < block; i += windowSize / 2
174 ) {
175 auto wcopy = std::min(windowSize, block - i);
176 for (decltype(wcopy) j = 0; j < wcopy; ++j)
177 datawindow[j] = buffer[i + j];
178 for (auto j = wcopy; j < windowSize; ++j)
179 datawindow[j] = 0;
180 mbDidSomething |= RemoveClicks(windowSize, datawindow.get());
181 for (decltype(wcopy) j = 0; j < wcopy; ++j)
182 buffer[i+j] = datawindow[j];
183 }
184
185 if (mbDidSomething) {
186 // RemoveClicks() actually did something.
187 if(!track.Set(
188 (samplePtr) buffer.get(), floatSample, start + s, block)) {
189 bResult = false;
190 break;
191 }
192 }
193 s += block;
194 if (TrackProgress(count, s.as_double() / len.as_double())) {
195 bResult = false;
196 break;
197 }
198 }
199 return bResult;
200}
201
202bool EffectClickRemoval::RemoveClicks(size_t len, float *buffer)
203{
204 bool bResult = false; // This effect usually does nothing.
205 size_t i;
206 size_t j;
207 int left = 0;
208
209 float msw;
210 int ww;
211 int s2 = sep/2;
212 Floats ms_seq{ len };
213 Floats b2{ len };
214
215 for( i=0; i<len; i++)
216 b2[i] = buffer[i]*buffer[i];
217
218 /* Shortcut for rms - multiple passes through b2, accumulating
219 * as we go.
220 */
221 for(i=0;i<len;i++)
222 ms_seq[i]=b2[i];
223
224 for(i=1; (int)i < sep; i *= 2) {
225 for(j=0;j<len-i; j++)
226 ms_seq[j] += ms_seq[j+i];
227 }
228
229 /* Cheat by truncating sep to next-lower power of two... */
230 sep = i;
231
232 for( i=0; i<len-sep; i++ ) {
233 ms_seq[i] /= sep;
234 }
235 /* ww runs from about 4 to mClickWidth. wrc is the reciprocal;
236 * chosen so that integer roundoff doesn't clobber us.
237 */
238 int wrc;
239 for(wrc=mClickWidth/4; wrc>=1; wrc /= 2) {
240 ww = mClickWidth/wrc;
241
242 for( i=0; i<len-sep; i++ ){
243 msw = 0;
244 for( j=0; (int)j<ww; j++) {
245 msw += b2[i+s2+j];
246 }
247 msw /= ww;
248
249 if(msw >= mThresholdLevel * ms_seq[i]/10) {
250 if( left == 0 ) {
251 left = i+s2;
252 }
253 } else {
254 if(left != 0 && ((int)i-left+s2) <= ww*2) {
255 float lv = buffer[left];
256 float rv = buffer[i+ww+s2];
257 for(j=left; j<i+ww+s2; j++) {
258 bResult = true;
259 buffer[j]= (rv*(j-left) + lv*(i+ww+s2-j))/(float)(i+ww+s2-left);
260 b2[j] = buffer[j]*buffer[j];
261 }
262 left=0;
263 } else if(left != 0) {
264 left = 0;
265 }
266 }
267 }
268 }
269 return bResult;
270}
271
272std::unique_ptr<EffectEditor> EffectClickRemoval::PopulateOrExchange(
274 const EffectOutputs *)
275{
276 mUIParent = S.GetParent();
277 S.AddSpace(0, 5);
278 S.SetBorder(10);
279
280 S.StartMultiColumn(3, wxEXPAND);
281 S.SetStretchyCol(2);
282 {
283 // Threshold
284 mThreshT = S.Id(ID_Thresh)
285 .Validator<IntegerValidator<int>>(
286 &mThresholdLevel, NumValidatorStyle::DEFAULT,
288 .AddTextBox(XXO("&Threshold (lower is more sensitive):"),
289 wxT(""),
290 10);
291
292 mThreshS = S.Id(ID_Thresh)
293 .Name(XO("Threshold"))
294 .Style(wxSL_HORIZONTAL)
295 .Validator<wxGenericValidator>(&mThresholdLevel)
296 .MinSize( { 150, -1 } )
297 .AddSlider( {}, mThresholdLevel, Threshold.max, Threshold.min);
298
299 // Click width
300 mWidthT = S.Id(ID_Width)
301 .Validator<IntegerValidator<int>>(
302 &mClickWidth, NumValidatorStyle::DEFAULT, Width.min, Width.max)
303 .AddTextBox(XXO("Max &Spike Width (higher is more sensitive):"),
304 wxT(""),
305 10);
306
307 mWidthS = S.Id(ID_Width)
308 .Name(XO("Max Spike Width"))
309 .Style(wxSL_HORIZONTAL)
310 .Validator<wxGenericValidator>(&mClickWidth)
311 .MinSize( { 150, -1 } )
312 .AddSlider( {}, mClickWidth, Width.max, Width.min);
313 }
314 S.EndMultiColumn();
315
316 return nullptr;
317}
318
320{
321 if (!mUIParent->TransferDataToWindow())
322 {
323 return false;
324 }
325
326 return true;
327}
328
330{
331 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
332 {
333 return false;
334 }
335
336 return true;
337}
338
339void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt))
340{
341 mWidthT->GetValidator()->TransferFromWindow();
342 mWidthS->GetValidator()->TransferToWindow();
343}
344
345void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt))
346{
347 mThreshT->GetValidator()->TransferFromWindow();
348 mThreshS->GetValidator()->TransferToWindow();
349}
350
351void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt))
352{
353 mWidthS->GetValidator()->TransferFromWindow();
354 mWidthT->GetValidator()->TransferToWindow();
355}
356
357void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt))
358{
359 mThreshS->GetValidator()->TransferFromWindow();
360 mThreshT->GetValidator()->TransferToWindow();
361}
wxT("CloseDown"))
END_EVENT_TABLE()
@ ID_Width
@ ID_Thresh
int min(int a, int b)
EffectType
@ EffectTypeProcess
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
char * samplePtr
Definition: SampleFormat.h:57
#define S(N)
Definition: ToChars.cpp:64
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
double mT1
Definition: EffectBase.h:116
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:109
double mT0
Definition: EffectBase.h:115
An Effect for removing clicks.
Definition: ClickRemoval.h:30
bool RemoveClicks(size_t len, float *buffer)
wxTextCtrl * mThreshT
Definition: ClickRemoval.h:84
static constexpr EffectParameter Width
Definition: ClickRemoval.h:91
const EffectParameterMethods & Parameters() const override
void OnWidthSlider(wxCommandEvent &evt)
bool CheckWhetherSkipEffect(const EffectSettings &settings) const override
After Init(), tell whether Process() should be skipped.
wxWeakRef< wxWindow > mUIParent
Definition: ClickRemoval.h:71
void OnThreshSlider(wxCommandEvent &evt)
EffectType GetType() const override
Type determines how it behaves.
wxTextCtrl * mWidthT
Definition: ClickRemoval.h:83
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
virtual ~EffectClickRemoval()
bool ProcessOne(int count, WaveChannel &track, sampleCount start, sampleCount len)
bool TransferDataToWindow(const EffectSettings &settings) override
static const ComponentInterfaceSymbol Symbol
Definition: ClickRemoval.h:34
wxSlider * mThreshS
Definition: ClickRemoval.h:82
TranslatableString GetDescription() const override
ComponentInterfaceSymbol GetSymbol() const override
std::unique_ptr< EffectEditor > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs) override
Add controls to effect panel; always succeeds.
void OnWidthText(wxCommandEvent &evt)
bool Process(EffectInstance &instance, EffectSettings &settings) override
static constexpr EffectParameter Threshold
Definition: ClickRemoval.h:89
wxSlider * mWidthS
Definition: ClickRemoval.h:81
void OnThreshText(wxCommandEvent &evt)
bool TransferDataFromWindow(EffectSettings &settings) override
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:343
Performs effect computation.
Use this object to copy the input tracks to tentative outputTracks.
Hold values to send to effect output meters.
Interface for manipulations of an Effect's settings.
static int DoMessageBox(const EffectPlugin &plugin, const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={})
Abstract base class used in importing a file.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
Holds a msgid for the translation catalog; may also bind format arguments.
bool Set(constSamplePtr buffer, sampleFormat format, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.cpp:3523
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
"narrow" overload fetches from the unique channel
Definition: WaveTrack.h:154
size_t GetMaxBlockSize() const
Definition: WaveTrack.h:1227
A Track that contains audio waveform data.
Definition: WaveTrack.h:222
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
BuiltinEffectsModule::Registration< EffectClickRemoval > reg
const Type min
Minimum value.
const Type max
Maximum value.
Externalized state of a plug-in.