1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Amplify.cpp
6
7 Dominic Mazzoni
8 Vaughan Johnson (Preview)
9
10 *******************************************************************//**
11
12 \class EffectAmplify
13 \brief An Effect that makes a sound louder or softer.
14
15 This rewritten class supports a smart Amplify effect - it calculates
16 the maximum amount of gain that can be applied to all tracks without
17 causing clipping and selects this as the default parameter.
18
19 *//*******************************************************************/
20
21
22 #include "Amplify.h"
23 #include "LoadEffects.h"
24
25 #include <math.h>
26 #include <float.h>
27
28 #include <wx/button.h>
29 #include <wx/checkbox.h>
30 #include <wx/intl.h>
31 #include <wx/sizer.h>
32 #include <wx/slider.h>
33 #include <wx/stattext.h>
34 #include <wx/textctrl.h>
35 #include <wx/valtext.h>
36 #include <wx/log.h>
37
38 #include "../Shuttle.h"
39 #include "../ShuttleGui.h"
40 #include "../WaveTrack.h"
41 #include "../widgets/valnum.h"
42
43
44 enum
45 {
46 ID_Amp = 10000,
47 ID_Peak,
48 ID_Clip
49 };
50
51 // Define keys, defaults, minimums, and maximums for the effect parameters
52 //
53 // Name Type Key Def Min Max Scale
54 Param( Ratio, float, wxT("Ratio"), 0.9f, 0.003162f, 316.227766f, 1.0f );
55 Param( Amp, float, wxT(""), -0.91515f, -50.0f, 50.0f, 10.0f );
56 Param( Clipping, bool, wxT("AllowClipping"), false, false, true, 1 );
57
58 //
59 // EffectAmplify
60 //
61
62 const ComponentInterfaceSymbol EffectAmplify::Symbol
63 { XO("Amplify") };
64
65 namespace{ BuiltinEffectsModule::Registration< EffectAmplify > reg; }
66
BEGIN_EVENT_TABLE(EffectAmplify,wxEvtHandler)67 BEGIN_EVENT_TABLE(EffectAmplify, wxEvtHandler)
68 EVT_SLIDER(ID_Amp, EffectAmplify::OnAmpSlider)
69 EVT_TEXT(ID_Amp, EffectAmplify::OnAmpText)
70 EVT_TEXT(ID_Peak, EffectAmplify::OnPeakText)
71 EVT_CHECKBOX(ID_Clip, EffectAmplify::OnClipCheckBox)
72 END_EVENT_TABLE()
73
74 EffectAmplify::EffectAmplify()
75 {
76 mAmp = DEF_Amp;
77 mRatio = DB_TO_LINEAR(mAmp);
78 mRatioClip = 0.0;
79 mCanClip = false;
80 mPeak = 0.0;
81
82 SetLinearEffectFlag(true);
83 }
84
~EffectAmplify()85 EffectAmplify::~EffectAmplify()
86 {
87 }
88
89 // ComponentInterface implementation
90
GetSymbol()91 ComponentInterfaceSymbol EffectAmplify::GetSymbol()
92 {
93 return Symbol;
94 }
95
GetDescription()96 TranslatableString EffectAmplify::GetDescription()
97 {
98 // Note: This is useful only after ratio has been set.
99 return XO("Increases or decreases the volume of the audio you have selected");
100 }
101
ManualPage()102 ManualPageID EffectAmplify::ManualPage()
103 {
104 return L"Amplify";
105 }
106
107 // EffectDefinitionInterface implementation
108
GetType()109 EffectType EffectAmplify::GetType()
110 {
111 return EffectTypeProcess;
112 }
113
114 // EffectClientInterface implementation
115
GetAudioInCount()116 unsigned EffectAmplify::GetAudioInCount()
117 {
118 return 1;
119 }
120
GetAudioOutCount()121 unsigned EffectAmplify::GetAudioOutCount()
122 {
123 return 1;
124 }
125
ProcessBlock(float ** inBlock,float ** outBlock,size_t blockLen)126 size_t EffectAmplify::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
127 {
128 for (decltype(blockLen) i = 0; i < blockLen; i++)
129 {
130 outBlock[0][i] = inBlock[0][i] * mRatio;
131 }
132
133 return blockLen;
134 }
DefineParams(ShuttleParams & S)135 bool EffectAmplify::DefineParams( ShuttleParams & S ){
136 S.SHUTTLE_PARAM( mRatio, Ratio );
137 if (!IsBatchProcessing())
138 S.SHUTTLE_PARAM( mCanClip, Clipping );
139 return true;
140 }
141
GetAutomationParameters(CommandParameters & parms)142 bool EffectAmplify::GetAutomationParameters(CommandParameters & parms)
143 {
144 parms.WriteFloat(KEY_Ratio, mRatio);
145 if (!IsBatchProcessing())
146 parms.WriteFloat(KEY_Clipping, mCanClip);
147
148 return true;
149 }
150
SetAutomationParameters(CommandParameters & parms)151 bool EffectAmplify::SetAutomationParameters(CommandParameters & parms)
152 {
153 ReadAndVerifyFloat(Ratio);
154 mRatio = Ratio;
155
156 if (!IsBatchProcessing()){
157 ReadAndVerifyBool(Clipping);
158 mCanClip = Clipping;
159 } else {
160 mCanClip = true;
161 }
162
163 return true;
164 }
165
LoadFactoryDefaults()166 bool EffectAmplify::LoadFactoryDefaults()
167 {
168 Init();
169
170 mRatioClip = 0.0;
171 if (mPeak > 0.0)
172 {
173 mRatio = 1.0 / mPeak;
174 mRatioClip = mRatio;
175 }
176 else
177 {
178 mRatio = 1.0;
179 }
180 mCanClip = false;
181
182 return TransferDataToWindow();
183 }
184
185 // Effect implementation
186
Init()187 bool EffectAmplify::Init()
188 {
189 mPeak = 0.0;
190
191 for (auto t : inputTracks()->Selected< const WaveTrack >())
192 {
193 auto pair = t->GetMinMax(mT0, mT1); // may throw
194 const float min = pair.first, max = pair.second;
195 float newpeak = (fabs(min) > fabs(max) ? fabs(min) : fabs(max));
196
197 if (newpeak > mPeak)
198 {
199 mPeak = newpeak;
200 }
201 }
202
203 return true;
204 }
205
Preview(bool dryOnly)206 void EffectAmplify::Preview(bool dryOnly)
207 {
208 auto cleanup1 = valueRestorer( mRatio );
209 auto cleanup2 = valueRestorer( mPeak );
210
211 Effect::Preview(dryOnly);
212 }
213
PopulateOrExchange(ShuttleGui & S)214 void EffectAmplify::PopulateOrExchange(ShuttleGui & S)
215 {
216 enum{ precision = 3 }; // allow (a generous) 3 decimal places for Amplification (dB)
217
218 bool batch = IsBatchProcessing();
219 if ( batch )
220 {
221 mCanClip = true;
222 mPeak = 1.0;
223 }
224 else
225 {
226 if (mPeak > 0.0)
227 {
228 mRatio = 1.0 / mPeak;
229 mRatioClip = mRatio;
230 }
231 else
232 {
233 mRatio = 1.0;
234 }
235 }
236
237 S.AddSpace(0, 5);
238
239 S.StartVerticalLay(0);
240 {
241 // Amplitude
242 S.StartMultiColumn(2, wxCENTER);
243 {
244 mAmpT = S.Id(ID_Amp)
245 .Validator<FloatingPointValidator<double>>(
246 precision, &mAmp, NumValidatorStyle::ONE_TRAILING_ZERO, MIN_Amp, MAX_Amp
247 )
248 .AddTextBox(XXO("&Amplification (dB):"), wxT(""), 12);
249 }
250 S.EndMultiColumn();
251
252 // Amplitude
253 S.StartHorizontalLay(wxEXPAND);
254 {
255 mAmpS = S.Id(ID_Amp)
256 .Style(wxSL_HORIZONTAL)
257 .Name(XO("Amplification dB"))
258 .AddSlider( {}, 0, MAX_Amp * SCL_Amp, MIN_Amp * SCL_Amp);
259 }
260 S.EndHorizontalLay();
261
262 // Peak
263 S.StartMultiColumn(2, wxCENTER);
264 {
265 mNewPeakT = S.Id(ID_Peak)
266 .Validator<FloatingPointValidator<double>>(
267 // One extra decimal place so that rounding is visible to user
268 // (see: bug 958)
269 precision + 1,
270 &mNewPeak, NumValidatorStyle::ONE_TRAILING_ZERO,
271 // min and max need same precision as what we're validating (bug 963)
272 RoundValue( precision + 1, MIN_Amp + LINEAR_TO_DB(mPeak) ),
273 RoundValue( precision + 1, MAX_Amp + LINEAR_TO_DB(mPeak) )
274 )
275 .AddTextBox(XXO("&New Peak Amplitude (dB):"), wxT(""), 12);
276 }
277 S.EndMultiColumn();
278
279 // Clipping
280 S.StartHorizontalLay(wxCENTER);
281 {
282
283 mClip = S.Id(ID_Clip).Disable( batch )
284 .AddCheckBox(XXO("Allo&w clipping"), false);
285 }
286 S.EndHorizontalLay();
287 }
288 S.EndVerticalLay();
289
290 return;
291 }
292
TransferDataToWindow()293 bool EffectAmplify::TransferDataToWindow()
294 {
295 // limit range of gain
296 double dBInit = LINEAR_TO_DB(mRatio);
297 double dB = TrapDouble(dBInit, MIN_Amp, MAX_Amp);
298 if (dB != dBInit)
299 mRatio = DB_TO_LINEAR(dB);
300
301 mAmp = LINEAR_TO_DB(mRatio);
302 mAmpT->GetValidator()->TransferToWindow();
303
304 mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5f));
305
306 mNewPeak = LINEAR_TO_DB(mRatio * mPeak);
307 mNewPeakT->GetValidator()->TransferToWindow();
308
309 mClip->SetValue(mCanClip);
310
311 CheckClip();
312
313 return true;
314 }
315
TransferDataFromWindow()316 bool EffectAmplify::TransferDataFromWindow()
317 {
318 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
319 {
320 return false;
321 }
322
323 mRatio = DB_TO_LINEAR(TrapDouble(mAmp * SCL_Amp, MIN_Amp * SCL_Amp, MAX_Amp * SCL_Amp) / SCL_Amp);
324
325 mCanClip = mClip->GetValue();
326
327 if (!mCanClip && mRatio * mPeak > 1.0)
328 {
329 mRatio = 1.0 / mPeak;
330 }
331
332 return true;
333 }
334
335 // EffectAmplify implementation
336
CheckClip()337 void EffectAmplify::CheckClip()
338 {
339 EnableApply(mClip->GetValue() || (mPeak > 0.0 && mRatio <= mRatioClip));
340 }
341
OnAmpText(wxCommandEvent & WXUNUSED (evt))342 void EffectAmplify::OnAmpText(wxCommandEvent & WXUNUSED(evt))
343 {
344 if (!mAmpT->GetValidator()->TransferFromWindow())
345 {
346 EnableApply(false);
347 return;
348 }
349
350 mRatio = DB_TO_LINEAR(TrapDouble(mAmp * SCL_Amp, MIN_Amp * SCL_Amp, MAX_Amp * SCL_Amp) / SCL_Amp);
351
352 mAmpS->SetValue((int) (LINEAR_TO_DB(mRatio) * SCL_Amp + 0.5));
353
354 mNewPeak = LINEAR_TO_DB(mRatio * mPeak);
355 mNewPeakT->GetValidator()->TransferToWindow();
356
357 CheckClip();
358 }
359
OnPeakText(wxCommandEvent & WXUNUSED (evt))360 void EffectAmplify::OnPeakText(wxCommandEvent & WXUNUSED(evt))
361 {
362 if (!mNewPeakT->GetValidator()->TransferFromWindow())
363 {
364 EnableApply(false);
365 return;
366 }
367
368 if (mNewPeak == 0.0)
369 mRatio = mRatioClip;
370 else
371 mRatio = DB_TO_LINEAR(mNewPeak) / mPeak;
372
373 double ampInit = LINEAR_TO_DB(mRatio);
374 mAmp = TrapDouble(ampInit, MIN_Amp, MAX_Amp);
375 if (mAmp != ampInit)
376 mRatio = DB_TO_LINEAR(mAmp);
377
378 mAmpT->GetValidator()->TransferToWindow();
379
380 mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5f));
381
382 CheckClip();
383 }
384
OnAmpSlider(wxCommandEvent & evt)385 void EffectAmplify::OnAmpSlider(wxCommandEvent & evt)
386 {
387 double dB = evt.GetInt() / SCL_Amp;
388 mRatio = DB_TO_LINEAR(TrapDouble(dB, MIN_Amp, MAX_Amp));
389
390 double dB2 = (evt.GetInt() - 1) / SCL_Amp;
391 double ratio2 = DB_TO_LINEAR(TrapDouble(dB2, MIN_Amp, MAX_Amp));
392
393 if (!mClip->GetValue() && mRatio * mPeak > 1.0 && ratio2 * mPeak < 1.0)
394 {
395 mRatio = 1.0 / mPeak;
396 }
397
398 mAmp = LINEAR_TO_DB(mRatio);
399 mAmpT->GetValidator()->TransferToWindow();
400
401 mNewPeak = LINEAR_TO_DB(mRatio * mPeak);
402 mNewPeakT->GetValidator()->TransferToWindow();
403
404 CheckClip();
405 }
406
OnClipCheckBox(wxCommandEvent & WXUNUSED (evt))407 void EffectAmplify::OnClipCheckBox(wxCommandEvent & WXUNUSED(evt))
408 {
409 CheckClip();
410 }
411