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.SetFloats(buffer.get(), start + s, block)) {
188 bResult = false;
189 break;
190 }
191 }
192 s += block;
193 if (TrackProgress(count, s.as_double() / len.as_double())) {
194 bResult = false;
195 break;
196 }
197 }
198 return bResult;
199}
200
201bool EffectClickRemoval::RemoveClicks(size_t len, float *buffer)
202{
203 bool bResult = false; // This effect usually does nothing.
204 size_t i;
205 size_t j;
206 int left = 0;
207
208 float msw;
209 int ww;
210 int s2 = sep/2;
211 Floats ms_seq{ len };
212 Floats b2{ len };
213
214 for( i=0; i<len; i++)
215 b2[i] = buffer[i]*buffer[i];
216
217 /* Shortcut for rms - multiple passes through b2, accumulating
218 * as we go.
219 */
220 for(i=0;i<len;i++)
221 ms_seq[i]=b2[i];
222
223 for(i=1; (int)i < sep; i *= 2) {
224 for(j=0;j<len-i; j++)
225 ms_seq[j] += ms_seq[j+i];
226 }
227
228 /* Cheat by truncating sep to next-lower power of two... */
229 sep = i;
230
231 for( i=0; i<len-sep; i++ ) {
232 ms_seq[i] /= sep;
233 }
234 /* ww runs from about 4 to mClickWidth. wrc is the reciprocal;
235 * chosen so that integer roundoff doesn't clobber us.
236 */
237 int wrc;
238 for(wrc=mClickWidth/4; wrc>=1; wrc /= 2) {
239 ww = mClickWidth/wrc;
240
241 for( i=0; i<len-sep; i++ ){
242 msw = 0;
243 for( j=0; (int)j<ww; j++) {
244 msw += b2[i+s2+j];
245 }
246 msw /= ww;
247
248 if(msw >= mThresholdLevel * ms_seq[i]/10) {
249 if( left == 0 ) {
250 left = i+s2;
251 }
252 } else {
253 if(left != 0 && ((int)i-left+s2) <= ww*2) {
254 float lv = buffer[left];
255 float rv = buffer[i+ww+s2];
256 for(j=left; j<i+ww+s2; j++) {
257 bResult = true;
258 buffer[j]= (rv*(j-left) + lv*(i+ww+s2-j))/(float)(i+ww+s2-left);
259 b2[j] = buffer[j]*buffer[j];
260 }
261 left=0;
262 } else if(left != 0) {
263 left = 0;
264 }
265 }
266 }
267 }
268 return bResult;
269}
270
271std::unique_ptr<EffectEditor> EffectClickRemoval::PopulateOrExchange(
273 const EffectOutputs *)
274{
275 mUIParent = S.GetParent();
276 S.AddSpace(0, 5);
277 S.SetBorder(10);
278
279 S.StartMultiColumn(3, wxEXPAND);
280 S.SetStretchyCol(2);
281 {
282 // Threshold
283 mThreshT = S.Id(ID_Thresh)
284 .Validator<IntegerValidator<int>>(
285 &mThresholdLevel, NumValidatorStyle::DEFAULT,
287 .AddTextBox(XXO("&Threshold (lower is more sensitive):"),
288 wxT(""),
289 10);
290
291 mThreshS = S.Id(ID_Thresh)
292 .Name(XO("Threshold"))
293 .Style(wxSL_HORIZONTAL)
294 .Validator<wxGenericValidator>(&mThresholdLevel)
295 .MinSize( { 150, -1 } )
296 .AddSlider( {}, mThresholdLevel, Threshold.max, Threshold.min);
297
298 // Click width
299 mWidthT = S.Id(ID_Width)
300 .Validator<IntegerValidator<int>>(
301 &mClickWidth, NumValidatorStyle::DEFAULT, Width.min, Width.max)
302 .AddTextBox(XXO("Max &Spike Width (higher is more sensitive):"),
303 wxT(""),
304 10);
305
306 mWidthS = S.Id(ID_Width)
307 .Name(XO("Max Spike Width"))
308 .Style(wxSL_HORIZONTAL)
309 .Validator<wxGenericValidator>(&mClickWidth)
310 .MinSize( { 150, -1 } )
311 .AddSlider( {}, mClickWidth, Width.max, Width.min);
312 }
313 S.EndMultiColumn();
314
315 return nullptr;
316}
317
319{
320 if (!mUIParent->TransferDataToWindow())
321 {
322 return false;
323 }
324
325 return true;
326}
327
329{
330 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
331 {
332 return false;
333 }
334
335 return true;
336}
337
338void EffectClickRemoval::OnWidthText(wxCommandEvent & WXUNUSED(evt))
339{
340 mWidthT->GetValidator()->TransferFromWindow();
341 mWidthS->GetValidator()->TransferToWindow();
342}
343
344void EffectClickRemoval::OnThreshText(wxCommandEvent & WXUNUSED(evt))
345{
346 mThreshT->GetValidator()->TransferFromWindow();
347 mThreshS->GetValidator()->TransferToWindow();
348}
349
350void EffectClickRemoval::OnWidthSlider(wxCommandEvent & WXUNUSED(evt))
351{
352 mWidthS->GetValidator()->TransferFromWindow();
353 mWidthT->GetValidator()->TransferToWindow();
354}
355
356void EffectClickRemoval::OnThreshSlider(wxCommandEvent & WXUNUSED(evt))
357{
358 mThreshS->GetValidator()->TransferFromWindow();
359 mThreshT->GetValidator()->TransferToWindow();
360}
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
#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:114
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:107
double mT0
Definition: EffectBase.h:113
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:640
Holds a msgid for the translation catalog; may also bind format arguments.
bool SetFloats(const float *buffer, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.h:162
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:129
size_t GetMaxBlockSize() const
Definition: WaveTrack.h:858
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
BuiltinEffectsModule::Registration< EffectClickRemoval > reg
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const Type min
Minimum value.
const Type max
Maximum value.
Externalized state of a plug-in.